From b2cb101befaa3febba0fc5fcfb77f28436891cc4 Mon Sep 17 00:00:00 2001 From: Viu Long Kong Date: Mon, 8 Jul 2019 17:37:51 +0200 Subject: [PATCH] v2.10.150 --- examples/cp/basic/plant_location_with_kpis.py | 2 +- examples/cp/jupyter/SteelMill.ipynb | 29 +- examples/cp/jupyter/golomb_ruler.ipynb | 39 +- examples/cp/jupyter/house_building.ipynb | 40 +- examples/cp/jupyter/n_queen.ipynb | 35 +- examples/cp/jupyter/sched_square.ipynb | 40 +- examples/cp/jupyter/scheduling_tuto.ipynb | 210 ++-- examples/cp/jupyter/sports_scheduling.ipynb | 61 +- examples/cp/jupyter/sudoku.ipynb | 33 +- examples/cp/jupyter/truck_fleet.ipynb | 33 +- .../mp/jupyter/Benders_decomposition.ipynb | 112 +-- examples/mp/jupyter/Progress_callbacks.ipynb | 94 +- examples/mp/jupyter/boxes.ipynb | 121 +-- .../mp/jupyter/chicago_coffee_shops.ipynb | 136 ++- examples/mp/jupyter/efficient.ipynb | 511 ++++++---- examples/mp/jupyter/green_truck.ipynb | 109 +-- .../mp/jupyter/incremental_modeling.ipynb | 139 +-- .../mp/jupyter/lagrangian_relaxation.ipynb | 58 +- examples/mp/jupyter/lifegame.ipynb | 77 +- examples/mp/jupyter/load_balancing.ipynb | 84 +- examples/mp/jupyter/logical_cts.ipynb | 546 ++++++++--- examples/mp/jupyter/marketing_campaign.ipynb | 94 +- examples/mp/jupyter/mining_pandas.ipynb | 151 +-- .../nurses_pandas-multi_objective.ipynb | 168 +--- examples/mp/jupyter/nurses_pandas.ipynb | 230 ++--- examples/mp/jupyter/nurses_scheduling.ipynb | 616 +++++++++--- examples/mp/jupyter/oil_blending.ipynb | 120 +-- examples/mp/jupyter/pasta_production.ipynb | 54 +- examples/mp/jupyter/progress.ipynb | 902 ++++++++++++++++++ .../sparktrans/SparkML_transformers.ipynb | 58 +- examples/mp/jupyter/sports_scheduling.ipynb | 132 +-- .../tutorials/Beyond_Linear_Programming.ipynb | 109 +-- .../tutorials/Linear_Programming.ipynb | 68 +- examples/mp/jupyter/ucp_pandas.ipynb | 200 ++-- examples/mp/modeling/diet.py | 4 +- examples/mp/modeling/nurses.py | 29 +- examples/mp/modeling/nurses_multiobj.py | 3 +- examples/mp/modeling/production.py | 3 + examples/mp/workflow/load_balancing.py | 275 +++--- 39 files changed, 3246 insertions(+), 2479 deletions(-) create mode 100644 examples/mp/jupyter/progress.ipynb diff --git a/examples/cp/basic/plant_location_with_kpis.py b/examples/cp/basic/plant_location_with_kpis.py index d66b986..15a5748 100644 --- a/examples/cp/basic/plant_location_with_kpis.py +++ b/examples/cp/basic/plant_location_with_kpis.py @@ -113,7 +113,7 @@ msol = mdl.solve(TimeLimit=10, trace_log=False) # Set trace_log=True to have a real-time view of the KPIs if msol: print(" Objective value: {}".format(msol.get_objective_values()[0])) - if context.model.version >= '12.9': + if compare_natural(context.model.version, '12.9') >= 0: print(" KPIs: {}".format(msol.get_kpis())) else: print(" No solution") diff --git a/examples/cp/jupyter/SteelMill.ipynb b/examples/cp/jupyter/SteelMill.ipynb index 4568675..0fa19a8 100644 --- a/examples/cp/jupyter/SteelMill.ipynb +++ b/examples/cp/jupyter/SteelMill.ipynb @@ -11,9 +11,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\n", - "\n", - ">It requires a **local installation of CPLEX Optimizers**. \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", "Table of contents:\n", "\n", @@ -316,9 +317,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(\"\\nSolving model....\")\n", @@ -338,9 +337,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Print solution\n", @@ -374,7 +371,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -388,23 +385,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/golomb_ruler.ipynb b/examples/cp/jupyter/golomb_ruler.ipynb index a54056e..63fc842 100644 --- a/examples/cp/jupyter/golomb_ruler.ipynb +++ b/examples/cp/jupyter/golomb_ruler.ipynb @@ -10,11 +10,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\n", - "\n", - ">It requires a **local installation of CPLEX Optimizers**. \n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", "Table of contents:\n", "\n", @@ -400,9 +399,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -422,9 +419,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Print solution\n", @@ -444,9 +439,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Print solution\n", @@ -474,9 +467,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Print solution as a ruler\n", @@ -514,7 +505,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -528,23 +519,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/house_building.ipynb b/examples/cp/jupyter/house_building.ipynb index bbdfb79..82b43c9 100644 --- a/examples/cp/jupyter/house_building.ipynb +++ b/examples/cp/jupyter/house_building.ipynb @@ -12,9 +12,11 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\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", - ">It requires a **local installation of CPLEX Optimizers**. \n", "\n", "\n", "Table of contents:\n", @@ -741,9 +743,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "obj = mdl.sum([s.level * mdl.presence_of(wtasks[(h, s)]) for s in SKILLS for h in HOUSES])\n", @@ -761,9 +761,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -781,9 +779,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(\"Solve status: \" + msol.get_solve_status())\n", @@ -881,9 +877,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol and visu.is_visu_enabled():\n", @@ -935,9 +929,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol and visu.is_visu_enabled():\n", @@ -986,7 +978,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -1000,23 +992,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/n_queen.ipynb b/examples/cp/jupyter/n_queen.ipynb index ee62a29..cdcba3f 100644 --- a/examples/cp/jupyter/n_queen.ipynb +++ b/examples/cp/jupyter/n_queen.ipynb @@ -11,9 +11,11 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\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", - ">It requires a **local installation of CPLEX Optimizers**. \n", "\n", "Table of contents:\n", "\n", @@ -232,9 +234,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(\"\\nSolving model....\")\n", @@ -315,9 +315,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol: \n", @@ -349,7 +347,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -359,27 +357,34 @@ "source": [ "Copyright © 2017, 2018 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/sched_square.ipynb b/examples/cp/jupyter/sched_square.ipynb index 560a0a0..dc40bf5 100644 --- a/examples/cp/jupyter/sched_square.ipynb +++ b/examples/cp/jupyter/sched_square.ipynb @@ -11,9 +11,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\n", - "\n", - ">It requires a **local installation of CPLEX Optimizers**. \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", "Table of contents:\n", "\n", @@ -280,9 +281,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "msol = mdl.solve(TimeLimit=20)" @@ -305,9 +304,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(\"Solution: \")\n", @@ -354,9 +351,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol and visu.is_visu_enabled():\n", @@ -398,7 +393,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -408,28 +403,35 @@ "source": [ "Copyright © 2017, 2018 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "celltoolbar": "Raw Cell Format", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/scheduling_tuto.ipynb b/examples/cp/jupyter/scheduling_tuto.ipynb index 26eed22..2b205d8 100644 --- a/examples/cp/jupyter/scheduling_tuto.ipynb +++ b/examples/cp/jupyter/scheduling_tuto.ipynb @@ -13,7 +13,10 @@ "source": [ "This notebook introduces the basic building blocks of a scheduling model that can be solved using *Constraint Programming Optimizer* (named CP Optimizer in the following) that is included in *CPLEX for Python*. \n", "\n", - "It is part of [Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html) and requires a **local installation of CPLEX Optimizers**. \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", "To follow the examples in this section, some knowledge about optimization (math programming or constraint programming) and about modeling optimization problems is necessary.\n", "For beginners in optimization, following the online free Decision Optimization tutorials ([here](https://ibmdecisionoptimization.github.io/tutorials/html/Linear_Programming.html) and [here](https://ibmdecisionoptimization.github.io/tutorials/html/Beyond_Linear_Programming.html)) might help to get a better understanding of Mathematical Optimization.\n", @@ -233,9 +236,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl0.add( mdl0.end_before_start(masonry, carpentry) )\n", @@ -272,9 +273,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -297,9 +296,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol0:\n", @@ -346,9 +343,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import docplex.cp.utils_visu as visu\n", @@ -362,9 +357,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol0:\n", @@ -700,9 +693,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "obj = mdl1.minimize( 400 * mdl1.max([mdl1.end_of(moving) - 100, 0]) \n", @@ -731,9 +722,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -745,9 +734,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol1:\n", @@ -801,9 +788,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol1:\n", @@ -1069,9 +1054,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "TaskNames_ids = {}\n", @@ -1100,9 +1083,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for h in Houses:\n", @@ -1168,9 +1149,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "transitionTimes = transition_matrix([[int(abs(i - j)) for j in Houses] for i in Houses])" @@ -1208,9 +1187,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "workers = {w : mdl2.sequence_var([ itvs[(h,t)] for h in Houses for t in TaskNames if Worker[t]==w ], \n", @@ -1240,9 +1217,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for w in WorkerNames:\n", @@ -1275,9 +1250,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# create the obj and add it.\n", @@ -1308,7 +1281,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -1323,7 +1295,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], @@ -1337,9 +1308,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Viewing the results of sequencing problems in a Gantt chart\n", @@ -1575,9 +1544,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from collections import namedtuple\n", @@ -1587,9 +1554,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "Calendar = {}\n", @@ -1623,9 +1588,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "#TaskNames_ids = {}\n", @@ -1653,9 +1616,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for h in Houses:\n", @@ -1683,9 +1644,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for w in WorkerNames:\n", @@ -1719,9 +1678,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for h in Houses:\n", @@ -1777,9 +1734,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -1791,9 +1746,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol3:\n", @@ -2004,9 +1957,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "itvs = {}\n", @@ -2072,9 +2023,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "workers_usage = step_at(0, 0)\n", @@ -2106,9 +2055,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "cash = step_at(0, 0)\n", @@ -2168,9 +2115,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl4.add( workers_usage <= NbWorkers )" @@ -2254,9 +2199,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -2268,9 +2211,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol4:\n", @@ -2506,7 +2447,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -2550,9 +2490,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "tasks = {}\n", @@ -2583,9 +2521,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for h in Houses:\n", @@ -2616,9 +2552,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for h in Houses:\n", @@ -2639,9 +2573,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for h in Houses:\n", @@ -2676,9 +2608,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for w in Workers:\n", @@ -2705,9 +2635,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl5.add(\n", @@ -2734,9 +2662,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -2748,9 +2674,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol5:\n", @@ -2890,9 +2814,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "NbHouses = 5\n", @@ -2960,9 +2882,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "task = {}\n", @@ -2993,9 +2913,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "workers = step_at(0, 0)\n", @@ -3024,9 +2942,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "Index = {s : i for i,s in enumerate(AllStates)}" @@ -3071,9 +2987,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "state = { h : state_function(ttime, name=\"house\"+str(h)) for h in Houses}" @@ -3104,9 +3018,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for h in Houses:\n", @@ -3140,9 +3052,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl6.add(mdl6.minimize( mdl6.max( mdl6.end_of(task[h,\"moving\"]) for h in Houses )))" @@ -3168,9 +3078,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Solve the model\n", @@ -3182,9 +3090,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol6:\n", @@ -3241,7 +3147,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -3256,23 +3162,23 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/sports_scheduling.ipynb b/examples/cp/jupyter/sports_scheduling.ipynb index 6be2ea1..4ed279f 100644 --- a/examples/cp/jupyter/sports_scheduling.ipynb +++ b/examples/cp/jupyter/sports_scheduling.ipynb @@ -13,9 +13,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\n", - "\n", - ">It requires a a **local installation of CPLEX Optimizers**. \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", "Table of contents:\n", "\n", @@ -105,9 +106,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -191,9 +190,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "CSS = \"\"\"\n", @@ -255,9 +252,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", @@ -280,9 +275,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.display import display\n", @@ -307,9 +300,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", @@ -348,9 +339,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.cp.model import *\n", @@ -435,9 +424,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Function that returns 1 if the two teams are in same division, 0 if not\n", @@ -500,9 +487,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "n = 25\n", @@ -521,9 +506,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol:\n", @@ -567,9 +550,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol:\n", @@ -610,7 +591,7 @@ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -625,23 +606,23 @@ "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/sudoku.ipynb b/examples/cp/jupyter/sudoku.ipynb index 4aa14d1..810e515 100644 --- a/examples/cp/jupyter/sudoku.ipynb +++ b/examples/cp/jupyter/sudoku.ipynb @@ -11,9 +11,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\n", - "\n", - ">It requires a **local installation of CPLEX Optimizers**. \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", "Table of contents:\n", "\n", @@ -291,9 +292,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display_grid(SUDOKU_PROBLEM_1, \"PROBLEM 1\")\n", @@ -454,9 +453,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(\"\\nSolving model....\")\n", @@ -473,9 +470,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display_grid(problem, \"Initial problem\")\n", @@ -503,7 +498,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -517,23 +512,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/cp/jupyter/truck_fleet.ipynb b/examples/cp/jupyter/truck_fleet.ipynb index 258301f..32b6641 100644 --- a/examples/cp/jupyter/truck_fleet.ipynb +++ b/examples/cp/jupyter/truck_fleet.ipynb @@ -11,9 +11,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)**\n", - "\n", - ">It requires a **local installation of CPLEX Optimizers**. \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", "Table of contents:\n", "\n", @@ -150,9 +151,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# List of possible truck configurations. Each tuple is (load, cost) with:\n", @@ -359,9 +358,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Search strategy: first assign order to truck\n", @@ -382,9 +379,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "if msol.is_solution():\n", @@ -420,7 +415,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](https://rawgit.com/IBMDecisionOptimization/docplex-doc/master/docs/index.html)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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" ] }, @@ -434,23 +429,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/Benders_decomposition.ipynb b/examples/mp/jupyter/Benders_decomposition.ipynb index ad9dec7..8e80a2c 100644 --- a/examples/mp/jupyter/Benders_decomposition.ipynb +++ b/examples/mp/jupyter/Benders_decomposition.ipynb @@ -3,8 +3,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -15,11 +13,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)**\n", - "\n", - ">It requires an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", "Table of contents:\n", "\n", @@ -79,8 +76,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -104,20 +99,14 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Use decision optimization" ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Step 1: Import the library\n", "\n", @@ -127,9 +116,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -149,8 +136,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -241,9 +226,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "deletable": true, - "editable": true + "collapsed": true }, "outputs": [], "source": [ @@ -256,9 +239,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "X = m.continuous_var_dict([(i,j) for i in R2 for j in R1])\n", @@ -271,9 +252,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.minimize( m.sum( Costs[i][j]*X[i,j] for i in R2 for j in R1) + sum(Y[i] for i in R1) )\n", @@ -287,8 +266,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -302,11 +279,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "m.print_information()" @@ -315,8 +288,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -327,7 +298,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], @@ -401,9 +371,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.parameters.benders.strategy = 3" @@ -412,9 +380,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.print_information()" @@ -430,9 +396,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "msol = m.solve(clean_before_solve=True)\n", @@ -500,9 +464,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for i in R2:\n", @@ -513,9 +475,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.print_information()" @@ -525,7 +485,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], @@ -564,9 +523,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "assert (obj1 == obj2) and (obj2 == obj3)" @@ -574,10 +531,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Summary\n", "\n", @@ -588,49 +542,51 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. Sample Materials." + "Copyright © 2017-2019 IBM. Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/Progress_callbacks.ipynb b/examples/mp/jupyter/Progress_callbacks.ipynb index e019fa2..a4f4074 100644 --- a/examples/mp/jupyter/Progress_callbacks.ipynb +++ b/examples/mp/jupyter/Progress_callbacks.ipynb @@ -3,8 +3,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -15,11 +13,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)**\n", - "\n", - ">It requires an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -37,8 +34,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -62,20 +57,14 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Use decision optimization" ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Step 1: Import the library\n", "\n", @@ -85,9 +74,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -100,8 +87,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -183,9 +168,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m5 = build_hearts(5)\n", @@ -198,8 +181,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -213,9 +194,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m5.solve(clean_before_solve=True)" @@ -257,9 +236,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.progress import SolutionListener\n", @@ -282,9 +259,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m5.solve(clean_before_solve=True)" @@ -301,9 +276,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for s in keeper.get_solutions():\n", @@ -324,9 +297,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.progress import ProgressListener\n", @@ -381,9 +352,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "auto_abort = AutomaticAborter(max_no_solutions=50)\n", @@ -394,10 +363,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Summary\n", "\n", @@ -408,49 +374,51 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. Sample Materials." + "Copyright © 2017-2019 IBM. Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/boxes.ipynb b/examples/mp/jupyter/boxes.ipynb index 3f6d590..53393e0 100644 --- a/examples/mp/jupyter/boxes.ipynb +++ b/examples/mp/jupyter/boxes.ipynb @@ -10,13 +10,10 @@ "\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", - ">Running the sample requires the installation of\n", - " [CPLEX Optimization studio](https://www.ibm.com/products/ilog-cplex-optimization-studio)\n", - " (Commercial or free \n", - " [CPLEX Community edition](https://www.ibm.com/account/reg/us-en/signup?formid=urx-20028>`)).\n", - " This sample automatically installs *CPLEX CE* if needed.\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", @@ -25,14 +22,14 @@ "* [How decision optimization (prescriptive analytics) can help](#How--decision-optimization-can-help)\n", "* [Use decision optimization](#Use-decision-optimization)\n", " * [Step 1: Import the library](#Step-1:-Import-the-library)\n", - " * [Step 2: Model the data](#Step-3:-Model-the-data)\n", - " * [Step 3: Prepare the data](#Step-4:-Prepare-the-data)\n", - " * [Step 4: Set up the prescriptive model](#Step-5:-Set-up-the-prescriptive-model)\n", + " * [Step 2: Model the data](#Step-2:-Model-the-data)\n", + " * [Step 3: Prepare the data](#Step-3:-Prepare-the-data)\n", + " * [Step 4: Set up the prescriptive model](#Step-4:-Set-up-the-prescriptive-model)\n", " * [Define the decision variables](#Define-the-decision-variables)\n", " * [Express the business constraints](#Express-the-business-constraints)\n", " * [Express the objective](#Express-the-objective)\n", - " * [Solve with Decision Optimization](#Solve-with-Decision-Optimization)\n", - " * [Step 5: Investigate the solution and run an example analysis](#Step-6:-Investigate-the-solution-and-then-run-an-example-analysis)\n", + " * [Solve the model](#Solve-the-model)\n", + " * [Step 5: Investigate the solution and run an example analysis](#Step-5:-Investigate-the-solution-and-then-run-an-example-analysis)\n", "* [Summary](#Summary)\n", "\n", "****" @@ -101,9 +98,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -122,10 +117,8 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -151,9 +144,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from math import sqrt\n", @@ -194,9 +185,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -215,9 +204,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -237,9 +224,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# decision variables is a 2d-matrix\n", @@ -258,9 +243,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# one object per box\n", @@ -286,9 +269,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# minimize total displacement\n", @@ -299,14 +280,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Solve with the model\n" + "#### Solve the model\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -319,9 +299,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.report()\n", @@ -359,9 +337,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraint(x[1,2] == 0)" @@ -377,9 +353,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints(x[k-1,1] >= x[k,2]\n", @@ -397,9 +371,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ok2 = mdl.solve()\n", @@ -434,9 +406,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# forall k in 2..N-1 then we can use the sum on the right hand side\n", @@ -475,9 +445,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", @@ -509,9 +477,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display_solution(sol1)" @@ -527,9 +493,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display_solution(sol2)" @@ -538,9 +502,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display_solution(sol3)" @@ -549,9 +511,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "\n", @@ -606,7 +566,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -614,29 +574,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/chicago_coffee_shops.ipynb b/examples/mp/jupyter/chicago_coffee_shops.ipynb index 2e64df5..304e45f 100644 --- a/examples/mp/jupyter/chicago_coffee_shops.ipynb +++ b/examples/mp/jupyter/chicago_coffee_shops.ipynb @@ -12,11 +12,10 @@ "\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 an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -27,7 +26,7 @@ " * [Step 1: Import the library](#Step-1:-Import-the-library)\n", " - [Step 2: Model the data](#Step-2:-Model-the-data)\n", " * [Step 3: Prepare the data](#Step-3:-Prepare-the-data)\n", - " - [Step 4: Set up the prescriptive model](#Step-5=4:-Set-up-the-prescriptive-model)\n", + " - [Step 4: Set up the prescriptive model](#Step-4:-Set-up-the-prescriptive-model)\n", " * [Define the decision variables](#Define-the-decision-variables)\n", " * [Express the business constraints](#Express-the-business-constraints)\n", " * [Express the objective](#Express-the-objective)\n", @@ -94,9 +93,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -150,9 +147,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Store longitude, latitude and street crossing name of each public library location.\n", @@ -184,9 +179,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -202,9 +195,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Simple distance computation between 2 locations.\n", @@ -228,26 +219,40 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, "render": false }, "outputs": [], "source": [ - "def build_libraries_from_url(url, name_pos, lat_long_pos):\n", + "def build_libraries_from_url(url):\n", " import requests\n", " import json\n", + " from six import iteritems\n", "\n", " r = requests.get(url)\n", " myjson = json.loads(r.text, parse_constant='utf-8')\n", - " myjson = myjson['data']\n", + " \n", + " # find columns for name and location\n", + " columns = myjson['meta']['view']['columns']\n", + " name_col = -1\n", + " location_col = -1\n", + " for (i, col) in enumerate(columns):\n", + " if col['name'].strip().lower() == 'name':\n", + " name_col = i\n", + " if col['name'].strip().lower() == 'location':\n", + " location_col = i\n", + " if (name_col == -1 or location_col == -1):\n", + " raise RuntimeError(\"Could not find name and location columns in data. Maybe format of %s changed?\" % url)\n", + " \n", + " # get library list\n", + " data = myjson['data']\n", "\n", " libraries = []\n", " k = 1\n", - " for location in myjson:\n", - " uname = location[name_pos]\n", + " for location in data:\n", + " uname = location[name_col]\n", " try:\n", - " latitude = float(location[lat_long_pos][1])\n", - " longitude = float(location[lat_long_pos][2])\n", + " latitude = float(location[location_col][1])\n", + " longitude = float(location[location_col][2])\n", " except TypeError:\n", " latitude = longitude = None\n", " try:\n", @@ -265,21 +270,16 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ - "libraries = build_libraries_from_url('https://data.cityofchicago.org/api/views/x8fc-8rcq/rows.json?accessType=DOWNLOAD',\n", - " name_pos=12,\n", - " lat_long_pos=18)" + "libraries = build_libraries_from_url('https://data.cityofchicago.org/api/views/x8fc-8rcq/rows.json?accessType=DOWNLOAD')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "render": false }, "outputs": [], @@ -299,7 +299,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "render": false }, "outputs": [], @@ -321,9 +320,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -340,7 +337,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -373,9 +369,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -394,9 +388,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -414,9 +406,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "BIGNUM = 999999999\n", @@ -446,9 +436,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for c_loc in coffeeshop_locations:\n", @@ -467,9 +455,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints(link_vars[c_loc, b] <= coffeeshop_vars[c_loc]\n", @@ -488,9 +474,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints(mdl.sum(link_vars[c_loc, b] for c_loc in coffeeshop_locations) == 1\n", @@ -508,9 +492,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Total nb of open coffee shops\n", @@ -532,9 +514,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Minimize total distance from points to hubs\n", @@ -554,9 +534,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(\"# coffee shops locations = %d\" % len(coffeeshop_locations))\n", @@ -571,7 +549,7 @@ "render": false }, "source": [ - "### Step 6: Investigate the solution and then run an example analysis\n", + "### Step 5: Investigate the solution and then run an example analysis\n", "\n", "The solution can be analyzed by displaying the location of the coffee shops on a map." ] @@ -580,7 +558,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "render": false }, "outputs": [], @@ -607,9 +584,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import folium\n", @@ -654,7 +629,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -662,31 +637,38 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "anaconda-cloud": {}, "celltoolbar": "Dashboard", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/efficient.ipynb b/examples/mp/jupyter/efficient.ipynb index b9f1281..34fd895 100644 --- a/examples/mp/jupyter/efficient.ipynb +++ b/examples/mp/jupyter/efficient.ipynb @@ -6,7 +6,12 @@ "source": [ "# Writing efficient DOcplex code\n", "\n", - "In this notebook, we show how to improve efficiency of DOcplex models using five simple rules." + "In this notebook, we show how to improve efficiency of DOcplex models using five simple rules.\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)." ] }, { @@ -22,11 +27,19 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin fibonacci 30\n", + "fibonacci(30) = 832040\n", + "<-- end fibonacci 30, time: 218 ms\n" + ] + } + ], "source": [ "import time\n", "import math\n", @@ -38,12 +51,13 @@ " \n", " def __enter__(self):\n", " self.start = time.time()\n", + " print('--> begin {0}'.format(self.msg))\n", " return self # return value is value of with ()\n", " \n", " def __exit__(self, *args):\n", " elapsed = time.time() - self.start\n", " self.msecs = math.ceil(1000* elapsed)\n", - " print('<-- {0}, time: {1:.0f} ms'.format(self.msg, self.msecs)) \n", + " print('<-- end {0}, time: {1:.0f} ms'.format(self.msg, self.msecs)) \n", " \n", "# try our timer on computing fibonacci numbers\n", "def fib(n):\n", @@ -82,7 +96,9 @@ "minimize \\sum_{k=0}^{k=N-1} (k+1) * y_{k}\\\\\n", "s.t.\\\\\n", "\\forall\\ \\ m\\ in \\{0..N-1\\}\\ \\ \\sum_{l=0}^{l=N-1} (y_{l} * (l+ (l+m) \\%3) \\ge l\\\\\n", - "y_{k} = 0, 1\n", + "y_{k} = 0, 1\\\\\n", + "\\\\\n", + "\\sum_{l} y_{l} \\ge 2\\\\\n", "$$" ] }, @@ -97,10 +113,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -109,11 +123,11 @@ " m = Model(name=\"bench1\")\n", " rsize = range(size)\n", " # create variables as a dictionary indexed by the range\n", - " ys = m.binary_var_dict(rsize, name=\"y\")\n", + " ys = m.binary_var_dict(rsize, name=\"my_yvar\")\n", " # create constraints\n", " k = {(i,j) : (i + (i+j) %3) for i in rsize for j in rsize}\n", " for i in rsize:\n", - " m.add(m.sum(ys[i] * k[i,j] for j in rsize) >= i, \"ct_%d\" %i)\n", + " m.add(m.sum(ys[i] * k[i,j] for j in rsize) >= i, \"ct_sum_yjs_%d\" %i)\n", " # for minimize, create a list of coefficients\n", " rsize1 = [i+1 for i in rsize]\n", " m.minimize(m.sum(ys[k] * rsize[k] for k in rsize))\n", @@ -129,11 +143,25 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin bench1_size_1000\n", + "<-- end bench1_size_1000, time: 4488 ms\n", + "Model: bench1\n", + " - number of variables: 1000\n", + " - binary=1000, integer=0, continuous=0\n", + " - number of constraints: 1000\n", + " - linear=1000\n", + " - parameters: defaults\n", + " - problem type is: MILP\n" + ] + } + ], "source": [ "with ContextTimer(\"bench1_size_1000\"):\n", " m11k = build_bench_model1(1000)\n", @@ -145,16 +173,23 @@ "metadata": {}, "source": [ "As expected the model has 1000 variables and 1000 constraints and the build time is significant.\n", - "For N=3000, we can expect an increase in buid time by a factor of 9:" + "For N=3000, we can expect an increase in buid time by a factor of 9, so you might as well go grab a coffee while this cell executes.." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin bench1 size=3000\n", + "<-- end bench1 size=3000, time: 39637 ms\n" + ] + } + ], "source": [ "N=3000\n", "with ContextTimer(\"bench1 size={0}\".format(N)):\n", @@ -165,14 +200,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Seven tips to improve DOcplex code efficiency" + "# Common tips to improve DOcplex code efficiency" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Use scalar product\n", + "## Tip #1: Use scalar product\n", "\n", "When building large expressions, scalar product (`Model.scal_prod()`) is \n", "an efficient way to combine a sequence of variables (or expressions)\n", @@ -182,18 +217,25 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin bench2 size=3000\n", + "<-- end bench2 size=3000, time: 24449 ms\n" + ] + } + ], "source": [ "def build_bench_model2(size=10):\n", " m = Model(name=\"bench2\")\n", " rsize = range(size)\n", " # create variables as a dictionary indexed by the range\n", " ys = m.binary_var_dict(rsize, name=\"y\")\n", - " # create constraints\n", + " # create a matrix of coefficients\n", " k = {(i,j) : (i + (i+j) %3) for i in rsize for j in rsize}\n", " for i in rsize:\n", " m.add(m.scal_prod([ys[i1] for i1 in rsize], [k[i,j] for j in rsize]) >= i, \"ct_%d\" % i)\n", @@ -202,7 +244,7 @@ " m.minimize(m.scal_prod([ys[k] for k in rsize], rsize1))\n", " return m\n", "\n", - "with ContextTimer(\"bench3 size={0}\".format(N)):\n", + "with ContextTimer(\"bench2 size={0}\".format(N)):\n", " build_bench_model2(N)" ] }, @@ -210,70 +252,49 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Use variable lists instead of dicts, if possible\n", + "## Tip #2 (variant) : try functional scalar product (a.k.a. dotf)\n", "\n", - "In the previous examples, we had to create an auxiliary sequence from the variable dictionary to pass to `scal_prod`. Actually, a variable _list_ would be much simpler to use than the dictionary, so we replace the `var_dict` by a `var_list`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "def build_bench_model21(size=10):\n", - " m = Model(name=\"bench2.1\")\n", - " rsize = range(size)\n", - " # create variables as a dictionary indexed by the range\n", - " ys = m.binary_var_list(rsize, name=\"y\")\n", - " # create constraints\n", - " k = {(i,j) : (i + (i+j) %3) for i in rsize for j in rsize}\n", - " for i in rsize:\n", - " m.add(m.scal_prod(ys, [k[i,j] for j in rsize]) >= i, \"ct_%d\" % i)\n", - " # for minimize, create a list of coefficients\n", - " rsize1 = [i+1 for i in rsize]\n", - " m.minimize(m.scal_prod(ys, rsize1))\n", - " return m\n", + "Since DOcplex 2.9, Scalar product has a _functional_ variant, in which coefficients are computed on the fly, with no need to prepare a (possibly large) container of numbers. In some cases, this can save a significant time.\n", "\n", - "with ContextTimer(\"bench2.1 size={0}\".format(N)):\n", - " build_bench_model21(N)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use batches of constraints\n", + "The method is `Model.dotf` takes two arguments:\n", "\n", - "Adding constraints to the model by batches using `Model.add_constraints()`\n", - "is usually more efficient.\n", - "Try grouping consttraints in lists or comprehensions (both work).\n", + " - a dictionary of variables, as create by the Model.xxx_var-dict methods\n", + " - a Python function that takes a variable key an dreturn a float, the coefficient \\\n", + " \n", + "In our example, keys are the integer from 0 to `size-1` .\n", + "The coefficient for y_j in the i_th constraint is i+(i+j)%3: here we do not need\n", + "to precompute a list or a comprehension, but only use a lambda function to compute this.\n", "\n", - "If the constraints are named, pass a second argument with the collection of constraint names." + "We also leverage `dotf` for the objective, where the cost coefficient for y_j is (j+1)" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin bench3 size=3000\n", + "<-- end bench3 size=3000, time: 15718 ms\n" + ] + } + ], "source": [ "def build_bench_model3(size=10):\n", " m = Model(name=\"bench3\")\n", " rsize = range(size)\n", " # create variables as a dictionary indexed by the range\n", - " ys = m.binary_var_list(rsize, name=\"y\")\n", - " # create constraints\n", - " k = {(i,j) : (i + (i+j) %3) for i in rsize for j in rsize}\n", - " m.add_constraints((m.scal_prod(ys, [k[i,j] for j in rsize]) >= i for i in rsize),\n", - " [\"ct_%d\" % i for i in rsize])\n", - " # for minimize, create a list of coefficients\n", - " rsize1 = [i+1 for i in rsize]\n", - " m.minimize(m.scal_prod(ys, rsize1))\n", + " ys = m.binary_var_dict(rsize, name=\"y\")\n", + " # no need to build an explicit matrix\n", + " #k = {(i,j) : (i + (i+j) %3) for i in rsize for j in rsize}\n", + " for i in rsize:\n", + " # the ith function is a function oj returning (i+(i+j)%3)\n", + " fi = lambda j_: i + (i+j_) % 3\n", + " m.add(m.dotf(ys, fi) >= i, \"ct_%d\" % i)\n", + " m.minimize(m.dotf(ys, lambda j_: j_ +1))\n", " return m\n", "\n", "with ContextTimer(\"bench3 size={0}\".format(N)):\n", @@ -284,29 +305,41 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Avoid creating unnecessary containers\n", + "## Tip #3: Add constraints in batches\n", + "\n", + "Adding constraints to the model by batches using `Model.add_constraints()`\n", + "is usually more efficient.\n", + "Try grouping constraints in lists or comprehensions (both work).\n", "\n", - "The previous version allocated one dictionary `k` for coefficients and an auxiliary list for the objective. We can simplify the code bys using comprehensions instead." + "If the constraints are named, pass a second argument with the collection of constraint names.\n", + "\n", + "Note that `Model.add` runs `Model.add_constraints` if passed an iterable." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin bench4 size=3000\n", + "<-- end bench4 size=3000, time: 15214 ms\n" + ] + } + ], "source": [ "def build_bench_model4(size=10):\n", " m = Model(name=\"bench4\")\n", " rsize = range(size)\n", " # create variables as a dictionary indexed by the range\n", - " ys = m.binary_var_list(rsize, name=\"y\")\n", + " ys = m.binary_var_dict(rsize, name=\"y\")\n", " # create constraints\n", - " m.add_constraints((m.scal_prod(ys, [(i+ (i+j)%3) for j in rsize]) >= i for i in rsize),\n", - " (\"ct_%d\" % i for i in rsize))\n", - " # for minimize, create a list of coefficients\n", - " m.minimize(m.scal_prod(ys, (i+1 for i in rsize)))\n", + " m.add_constraints((m.dotf(ys, lambda j_: i + (i+j_) % 3) >= i for i in rsize),\n", + " (\"ct_%d\" % i for i in rsize))\n", + " m.minimize(m.dotf(ys, lambda j_: j_ +1))\n", " return m\n", "\n", "with ContextTimer(\"bench4 size={0}\".format(N)):\n", @@ -317,38 +350,42 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Take control of name generation\n", - "\n", - "Naming variables and/or constraints is useful to generate readable LP files. However, generating lare numbers of strings may have a significant cost in Python, sepcially for large models. \n", + "## Tip #4: take control of name generation\n", "\n", - "DOcplex provides a keyword `ignore_names` at model creation time, which may disable all names (variables and constraint names alike) that are set in the model. \n", + "Naming variables and/or constraints is useful to generate readable LP files. However, generating large numbers of strings may have a significant cost in Python, especially for large models. \n", "\n", - "By default, this flag is `False`, and names are enabled. By setting this flag to `True`, all names mentioned in the model are discarded (in particular, names are not used in LP generation).\n", + "Passing `ignore_names=True` at model creation time, disables all name generation (variables and constraint names alike) everywhere in the model. \n", "\n", - "In the next version we simply add the `ignore_names=True` keyword argument to the model constructor" + "By default, names are active and this flag is `False`.\n" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin bench5 size=3000\n", + "<-- end bench5 size=3000, time: 14938 ms\n" + ] + } + ], "source": [ "def build_bench_model5(size=10):\n", " m = Model(name=\"bench5\", ignore_names=True)\n", " rsize = range(size)\n", " # create variables as a dictionary indexed by the range\n", - " ys = m.binary_var_list(rsize, name=\"y\")\n", + " ys = m.binary_var_dict(rsize, name=\"y\")\n", " # create constraints\n", - " m.add_constraints((m.scal_prod(ys, [(i+ (i+j)%3) for j in rsize]) >= i for i in rsize),\n", - " (\"ct_%d\" % i for i in rsize))\n", - " # for minimize, create a list of coefficients\n", - " m.minimize(m.scal_prod(ys, (i+1 for i in rsize)))\n", + " m.add((m.dotf(ys, lambda j_: i + (i+j_) % 3) >= i for i in rsize),\n", + " (\"ct_%d\" % i for i in rsize))\n", + " m.minimize(m.dotf(ys, lambda j_: j_ +1))\n", " return m\n", "\n", - "with ContextTimer(\"bench6 size={0}\".format(N)):\n", + "with ContextTimer(\"bench5 size={0}\".format(N)):\n", " build_bench_model5(N)" ] }, @@ -356,101 +393,59 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Take control of argument checking\n", + "## Tip #5: take control of argument checking\n", "\n", - "DOcplex does a minimal checking on arguments. As this can be useful when writing the model to avoid errors, this checking has a runtime cost. When running a deployed model that has been thoroughly tested and tuned, you can remove all checks by adding the `checker=\"off\"` keyword argument to the model constructor.\n", + "DOcplex usually checks the argumens passed to methods. As this can be useful when writing the model to avoid errors, this checking comes with a runtime cost. When running a deployed model that has been thoroughly tested and tuned, you can remove all checks by adding the `checker=\"off\"` keyword argument to the model constructor.\n", "\n", "Again, the next version is identical to the previous one , except that type-checking has been disabled." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--> begin bench6 size=3000\n", + "<-- end bench6 size=3000, time: 11661 ms\n" + ] + } + ], "source": [ "def build_bench_model6(size=10):\n", - " m = Model(name=\"bench6\", ignore_names=True, checker=\"off\")\n", + " m = Model(name=\"bench6\", ignore_names=True, checker='off')\n", " rsize = range(size)\n", " # create variables as a dictionary indexed by the range\n", - " ys = m.binary_var_list(rsize, name=\"y\")\n", + " ys = m.binary_var_dict(rsize, name=\"y\")\n", " # create constraints\n", - " m.add_constraints((m.scal_prod(ys, [(i+ (i+j)%3) for j in rsize]) >= i for i in rsize),\n", - " (\"ct_%d\" % i for i in rsize))\n", - " # for minimize, create a list of coefficients\n", - " m.minimize(m.scal_prod(ys, (i+1 for i in rsize)))\n", + " m.add((m.dotf(ys, lambda j_: i + (i+j_) % 3) >= i for i in rsize),\n", + " (\"ct_%d\" % i for i in rsize))\n", + " m.add(m.sum(ys) >= 2, \"sum_ys_ge_2\")\n", + " m.minimize(m.dotf(ys, lambda j_: j_ +1))\n", " return m\n", "\n", "with ContextTimer(\"bench6 size={0}\".format(N)):\n", " build_bench_model6(N)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use the \"advanced model\" class\n", - "\n", - "DOcplex contains and `AdvModel` class, which is a subclass of `Model`.\n", - "This class contains highly efficient methods for special cases, for examples, scalar prodicts with all different variables. \n", - "By default, `AdvModel` methods do not perform any checking on their arguments. For example, the `cal_prod_vars_all_different` method does not check that the variables are indeed all different. If this is not true, then errors or incorrect results may occur.\n", - "\n", - "Still, you can enable type-checking on AdvModel by specifying `checker=\"on\"` at construction time, and remove it later.\n", - "\n", - "The benchmark model is a good fit for the `scal_prod_vars_all_different` method as all our scalar products involve the `ys` variables.\n", - "\n", - "AdvModel provides other specialized fast method, among them:\n", - "\n", - " - `sum_vars_all_different` to compute the sum of a sequence of all different variables.\n", - " - `quad_matrix_sum` to compute a quadratic expression from a matrix $Q$ and a vector of variables $X$ as $X^{t}QX$\n", - " - `vector_compare` to compute a sequence of linear constraint from two sequences of expressions\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "from docplex.mp.advmodel import AdvModel\n", - "\n", - "def build_bench_model7(size=10):\n", - " m = AdvModel(name=\"bench7\", ignore_names=True, checker='off')\n", - " rsize = range(size)\n", - " # create variables as a dictionary indexed by the range\n", - " ys = m.binary_var_list(rsize, name=\"y\")\n", - " # create constraints\n", - " m.add_constraints((m.scal_prod_vars_all_different(ys, [(i+ (i+j)%3) for j in rsize]) >= i for i in rsize),\n", - " (\"ct_%d\" % i for i in rsize))\n", - " \n", - " # for minimize, use comprehension\n", - " m.minimize(m.scal_prod_vars_all_different(ys, (i+1 for i in rsize)))\n", - " return m\n", - "\n", - "with ContextTimer(\"bench7 size={0}\".format(N)):\n", - " build_bench_model7(N)" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Summary\n", "\n", - "From version 1 to version 7 , model build time has decreased from 35s to 4s (on our platform). Result smay well differ on other platforms, but still, this demonstrates that the way the model is built can greatly influence the performance.\n", + "From version 1 to version 6 , model build time has decreased from 35s to 4s (on our platform). Result smay well differ on other platforms, but still, this demonstrates that the way the model is built can greatly influence the performance.\n", "\n", "Here is a list of tricks to try to improve model building time:\n", "\n", " - Use Model.scal_prod wherever possible\n", + " - Try using Model.dotf when applicable.\n", " - Add constraints in batches, not one by one\n", - " - Leverage Python comprehensions to avoid unnecessary data structures\n", " - Try ignoring name generation (for large models)\n", - " - Try disabling all argument checking\n", - " - Eventually, look at specialized methods in the `AdvModel` class." + " - Try disabling argument checking\n" ] }, { @@ -469,11 +464,91 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* start computing performance data\n", + "* start computing results...\n", + "--> begin [1/36] use build_bench_model1 with size=100\n", + "<-- end [1/36] use build_bench_model1 with size=100, time: 66 ms\n", + "--> begin [2/36] use build_bench_model2 with size=100\n", + "<-- end [2/36] use build_bench_model2 with size=100, time: 43 ms\n", + "--> begin [3/36] use build_bench_model3 with size=100\n", + "<-- end [3/36] use build_bench_model3 with size=100, time: 45 ms\n", + "--> begin [4/36] use build_bench_model4 with size=100\n", + "<-- end [4/36] use build_bench_model4 with size=100, time: 32 ms\n", + "--> begin [5/36] use build_bench_model5 with size=100\n", + "<-- end [5/36] use build_bench_model5 with size=100, time: 33 ms\n", + "--> begin [6/36] use build_bench_model6 with size=100\n", + "<-- end [6/36] use build_bench_model6 with size=100, time: 29 ms\n", + "--> begin [7/36] use build_bench_model1 with size=300\n", + "<-- end [7/36] use build_bench_model1 with size=300, time: 358 ms\n", + "--> begin [8/36] use build_bench_model2 with size=300\n", + "<-- end [8/36] use build_bench_model2 with size=300, time: 211 ms\n", + "--> begin [9/36] use build_bench_model3 with size=300\n", + "<-- end [9/36] use build_bench_model3 with size=300, time: 255 ms\n", + "--> begin [10/36] use build_bench_model4 with size=300\n", + "<-- end [10/36] use build_bench_model4 with size=300, time: 216 ms\n", + "--> begin [11/36] use build_bench_model5 with size=300\n", + "<-- end [11/36] use build_bench_model5 with size=300, time: 174 ms\n", + "--> begin [12/36] use build_bench_model6 with size=300\n", + "<-- end [12/36] use build_bench_model6 with size=300, time: 141 ms\n", + "--> begin [13/36] use build_bench_model1 with size=600\n", + "<-- end [13/36] use build_bench_model1 with size=600, time: 1324 ms\n", + "--> begin [14/36] use build_bench_model2 with size=600\n", + "<-- end [14/36] use build_bench_model2 with size=600, time: 818 ms\n", + "--> begin [15/36] use build_bench_model3 with size=600\n", + "<-- end [15/36] use build_bench_model3 with size=600, time: 619 ms\n", + "--> begin [16/36] use build_bench_model4 with size=600\n", + "<-- end [16/36] use build_bench_model4 with size=600, time: 564 ms\n", + "--> begin [17/36] use build_bench_model5 with size=600\n", + "<-- end [17/36] use build_bench_model5 with size=600, time: 540 ms\n", + "--> begin [18/36] use build_bench_model6 with size=600\n", + "<-- end [18/36] use build_bench_model6 with size=600, time: 369 ms\n", + "--> begin [19/36] use build_bench_model1 with size=1000\n", + "<-- end [19/36] use build_bench_model1 with size=1000, time: 3748 ms\n", + "--> begin [20/36] use build_bench_model2 with size=1000\n", + "<-- end [20/36] use build_bench_model2 with size=1000, time: 2663 ms\n", + "--> begin [21/36] use build_bench_model3 with size=1000\n", + "<-- end [21/36] use build_bench_model3 with size=1000, time: 1562 ms\n", + "--> begin [22/36] use build_bench_model4 with size=1000\n", + "<-- end [22/36] use build_bench_model4 with size=1000, time: 1802 ms\n", + "--> begin [23/36] use build_bench_model5 with size=1000\n", + "<-- end [23/36] use build_bench_model5 with size=1000, time: 1527 ms\n", + "--> begin [24/36] use build_bench_model6 with size=1000\n", + "<-- end [24/36] use build_bench_model6 with size=1000, time: 1092 ms\n", + "--> begin [25/36] use build_bench_model1 with size=3000\n", + "<-- end [25/36] use build_bench_model1 with size=3000, time: 34271 ms\n", + "--> begin [26/36] use build_bench_model2 with size=3000\n", + "<-- end [26/36] use build_bench_model2 with size=3000, time: 21442 ms\n", + "--> begin [27/36] use build_bench_model3 with size=3000\n", + "<-- end [27/36] use build_bench_model3 with size=3000, time: 13377 ms\n", + "--> begin [28/36] use build_bench_model4 with size=3000\n", + "<-- end [28/36] use build_bench_model4 with size=3000, time: 13483 ms\n", + "--> begin [29/36] use build_bench_model5 with size=3000\n", + "<-- end [29/36] use build_bench_model5 with size=3000, time: 14593 ms\n", + "--> begin [30/36] use build_bench_model6 with size=3000\n", + "<-- end [30/36] use build_bench_model6 with size=3000, time: 12268 ms\n", + "--> begin [31/36] use build_bench_model1 with size=5000\n", + "<-- end [31/36] use build_bench_model1 with size=5000, time: 97333 ms\n", + "--> begin [32/36] use build_bench_model2 with size=5000\n", + "<-- end [32/36] use build_bench_model2 with size=5000, time: 61049 ms\n", + "--> begin [33/36] use build_bench_model3 with size=5000\n", + "<-- end [33/36] use build_bench_model3 with size=5000, time: 37885 ms\n", + "--> begin [34/36] use build_bench_model4 with size=5000\n", + "<-- end [34/36] use build_bench_model4 with size=5000, time: 38767 ms\n", + "--> begin [35/36] use build_bench_model5 with size=5000\n", + "<-- end [35/36] use build_bench_model5 with size=5000, time: 40835 ms\n", + "--> begin [36/36] use build_bench_model6 with size=5000\n", + "<-- end [36/36] use build_bench_model6 with size=5000, time: 26912 ms\n", + "* end computing results\n" + ] + } + ], "source": [ "# various sizes to sample performance\n", "sizes = [100, 300, 600, 1000, 3000, 5000]\n", @@ -481,10 +556,11 @@ "# a lits of tuples (fn, label) to build model and an explanatory label\n", "builders = [(build_bench_model1, \"initial\"), \n", " (build_bench_model2, \"scal_prod\"),\n", + " (build_bench_model3, \"dotf\"),\n", " (build_bench_model4, \"batch_cts\"),\n", " (build_bench_model5, \"ignore_names\"),\n", - " (build_bench_model6, \"checker_off\"),\n", - " (build_bench_model7, \"advmodel\")]\n", + " (build_bench_model6, \"names_checker_off\")\n", + " ]\n", "print(\"* start computing performance data\")\n", "res = {}\n", "print(\"* start computing results...\")\n", @@ -505,11 +581,22 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "try:\n", " import matplotlib.pyplot as plt\n", @@ -540,19 +627,26 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* geometric mean of time improvement is 3.0\n" + ] + } + ], "source": [ "# compute geomerical mean for all sizes\n", "nb_builders = len(builders)\n", "ratios = {}\n", "for s in sizes:\n", " initial = res[0, s]\n", - " final = res[nb_builders-1, s]\n", - " r = (initial/final)\n", + " # compute best over all builds\n", + " best = min(res[b, s] for b in range(nb_builders))\n", + " r = (initial/best)\n", " ratios[s] = r\n", "import math\n", "rgm = math.exp(sum(math.log(r) for r in ratios.values()) / float(nb_builders))\n", @@ -560,32 +654,39 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "collapsed": true }, + "source": [ + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "531_36", "language": "python", - "name": "python2" + "name": "531_36" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.8" } }, "nbformat": 4, diff --git a/examples/mp/jupyter/green_truck.ipynb b/examples/mp/jupyter/green_truck.ipynb index 434c16f..7ddc126 100644 --- a/examples/mp/jupyter/green_truck.ipynb +++ b/examples/mp/jupyter/green_truck.ipynb @@ -13,11 +13,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the [Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", - "\n", - ">It requires an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -98,9 +97,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -155,9 +152,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "_parameters = namedtuple('parameters', ['maxTrucks', 'maxVolume'])\n", @@ -174,9 +169,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import requests\n", @@ -227,9 +220,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "CSS = \"\"\"\n", @@ -278,9 +269,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "parameters = read_json_tuple(name='Parameters', my_namedtuple=_parameters)\n", @@ -309,9 +298,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "maxTrucks = parameters.maxTrucks;\n", @@ -403,9 +390,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -444,9 +429,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "truckOnRoute = model.integer_var_matrix(keys1=routes, keys2=truckTypeIds, lb=0, ub=maxTrucks, name=\"TruckOnRoute\")\n", @@ -482,9 +465,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for r in routes:\n", @@ -502,9 +483,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for (s,h,dist) in routes:\n", @@ -547,9 +526,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for (o,d,v) in shipments:\n", @@ -568,9 +545,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for (s,h,dist) in routes:\n", @@ -604,9 +579,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "totalCost = model.sum(2 * r.distance * truckTypeInfos[t].costPerMile * truckOnRoute[r, t] for r in routes for t in truckTypeIds)\n", @@ -628,9 +601,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "model.print_information()\n", @@ -666,9 +637,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Post processing: result data structures are exported as post-processed tuple or list of tuples\n", @@ -723,7 +692,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -734,9 +702,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display(inVolumeThroughHubOnTruckRes)" @@ -745,9 +711,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display(outVolumeThroughHubOnTruckRes)" @@ -756,9 +720,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display(inBoundAggregated)" @@ -767,9 +729,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display(outBoundAggregated)" @@ -794,7 +754,7 @@ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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" ] }, @@ -802,30 +762,37 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/incremental_modeling.ipynb b/examples/mp/jupyter/incremental_modeling.ipynb index 2828cd0..6732c52 100644 --- a/examples/mp/jupyter/incremental_modeling.ipynb +++ b/examples/mp/jupyter/incremental_modeling.ipynb @@ -3,8 +3,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -21,11 +19,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)**\n", - "\n", - ">It requires an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -43,8 +40,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -73,8 +68,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -98,20 +91,14 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Use decision optimization" ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Step 1: Import the library\n", "\n", @@ -121,9 +108,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -143,8 +128,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -181,9 +164,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "deletable": true, - "editable": true + "collapsed": true }, "outputs": [], "source": [ @@ -197,8 +178,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -210,9 +189,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "deletable": true, - "editable": true + "collapsed": true }, "outputs": [], "source": [ @@ -248,8 +225,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -263,11 +238,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "m.print_information()\n", @@ -277,9 +248,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "assert msol is not None, \"model can't solve\"\n", @@ -289,8 +258,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -323,9 +290,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Access by name\n", @@ -341,9 +306,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "msol = m.solve()\n", @@ -396,9 +359,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.add_constraint(hybrid >= 350)\n", @@ -422,9 +383,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.get_objective_expr().add_term(hybrid, 10)\n", @@ -458,9 +417,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.get_constraint_by_name(\"assembly_limit\").lhs.add_term(hybrid, 0.2)\n", @@ -478,9 +435,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "msol = m.solve()\n", @@ -499,9 +454,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ct_painting.lhs.set_coefficients([(desk, 0.1), (cell, 0.1), (hybrid, 0.1)])" @@ -510,9 +463,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "msol = m.solve()\n", @@ -550,7 +501,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -585,9 +535,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ct_polishing.name = \"high_\"+ct_polishing.name\n", @@ -598,9 +546,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# if a name contains \"low\", it has priority LOW\n", @@ -619,9 +565,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.print_solution()" @@ -630,9 +574,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ct_polishing_relax = relaxer.get_relaxation(ct_polishing)\n", @@ -645,10 +587,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Summary\n", "\n", @@ -659,49 +598,51 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. Sample Materials." + "Copyright © 2017-2019 IBM. Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/lagrangian_relaxation.ipynb b/examples/mp/jupyter/lagrangian_relaxation.ipynb index e210da9..f24d60f 100644 --- a/examples/mp/jupyter/lagrangian_relaxation.ipynb +++ b/examples/mp/jupyter/lagrangian_relaxation.ipynb @@ -11,13 +11,12 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the [Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)\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", - ">It requires an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\n", - "\n", - "Some familiarity with Python is recommended. This notebook runs on Python 2." + "Some familiarity with Python is recommended." ] }, { @@ -112,9 +111,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -195,9 +192,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -244,7 +239,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [], @@ -268,7 +262,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -298,9 +291,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "s = mdl.solve()\n", @@ -343,9 +334,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "#p_vars are the penalties attached to violating the constraints\n", @@ -355,9 +344,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# new version of the approximated constraint where we apply the penalties\n", @@ -393,7 +380,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -416,9 +402,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "while loop_count <= max_iters:\n", @@ -458,9 +442,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(best)" @@ -502,7 +484,7 @@ "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", "* [Decision Optimization documentation](https://datascience.ibm.com/docs/content/DO/DOinDSX.html)\n", - "* For help with DOcplex, or to report a defect, go [here](https://developer.ibm.com/answers/smartspace/docloud).\n", + "* For help with DOcplex, or to report a defect, go [here](https://stackoverflow.com/questions/tagged/docplex).\n", "* Contact us at dofeedback@wwpdl.vnet.ibm.com\n" ] }, @@ -511,7 +493,7 @@ "metadata": {}, "source": [ "
\n", - "Copyright © IBM Corp. 2017-2018. Released as licensed Sample Materials." + "Copyright © IBM Corp. 2017-2019. Released as licensed Sample Materials." ] }, { @@ -527,23 +509,23 @@ "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/lifegame.ipynb b/examples/mp/jupyter/lifegame.ipynb index 4d7de0d..a5904fd 100644 --- a/examples/mp/jupyter/lifegame.ipynb +++ b/examples/mp/jupyter/lifegame.ipynb @@ -3,8 +3,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -15,12 +13,12 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)**\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", "This model is greater than the size allowed in trial mode of CPLEX.\n", - ">It requires a **local installation of CPLEX Optimizers**. \n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\n", "\n", "\n", "Table of contents:\n", @@ -55,8 +53,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -80,20 +76,14 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Use decision optimization" ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Step 1: Import the library\n", "\n", @@ -103,9 +93,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -125,8 +113,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -294,9 +280,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "n2 = int(math.ceil(n/2))\n", @@ -377,7 +361,7 @@ " vvmap[life_vars[i+1, j]] = 1\n", " vvmap[life_vars[i, j+1]] = 1\n", " vvmap[life_vars[i+1, j+1]] = 1\n", - "ini_s = lm.new_solution(var_value_dict=vvmap)" + "ini_s = lm.new_solution(vvmap)" ] }, { @@ -412,9 +396,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "assert lm.solve(log_output=True), \"!!! Solve of the model fails\"\n", @@ -439,9 +421,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(lifegame_solution_to_matrix(lm))" @@ -449,10 +429,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Summary\n", "\n", @@ -463,50 +440,52 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. Sample Materials." + "Copyright © 2017-2019 IBM. Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "anaconda-cloud": {}, "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/load_balancing.ipynb b/examples/mp/jupyter/load_balancing.ipynb index 22d3d78..de80067 100644 --- a/examples/mp/jupyter/load_balancing.ipynb +++ b/examples/mp/jupyter/load_balancing.ipynb @@ -13,11 +13,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the [Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", - "\n", - ">It requires an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -92,9 +91,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -128,9 +125,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "class TUser(namedtuple(\"TUser\", [\"id\", \"running\", \"sleeping\", \"current_server\"])):\n", @@ -155,9 +150,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -169,9 +162,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import csv\n", @@ -201,9 +192,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "max_processes_per_server = 50\n", @@ -226,9 +215,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -267,9 +254,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "active_var_by_server = mdl.binary_var_dict(servers, name='isActive')\n", @@ -310,9 +295,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints(\n", @@ -339,9 +322,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# sum of assignment vars for (u, all s in servers) == 1\n", @@ -354,9 +335,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "number_of_active_servers = mdl.sum((active_var_by_server[svr] for svr in servers))\n", @@ -389,9 +368,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Set objective function\n", @@ -414,9 +391,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# build an ordered sequence of goals\n", @@ -439,9 +414,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "active_servers = sorted([s for s in servers if active_var_by_server[s].solution_value == 1])\n", @@ -479,7 +452,7 @@ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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" ] }, @@ -487,30 +460,37 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/logical_cts.ipynb b/examples/mp/jupyter/logical_cts.ipynb index 199379c..127baed 100644 --- a/examples/mp/jupyter/logical_cts.ipynb +++ b/examples/mp/jupyter/logical_cts.ipynb @@ -3,8 +3,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -15,11 +13,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the **[Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)**\n", - "\n", - ">It requires an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -39,15 +36,13 @@ "metadata": {}, "source": [ "Logical constraints let you use the _truth value_ of constraints inside the model. The truth value of a constraint \n", - "is true when it is satisfied and false when not. Adding a constraint to a model ensures that it is always satisfied. \n", - "However, with logical constraints, one can use the truth value of a constraint _inside_ the model, allowing to choose dynamically whether a constraint is to be satisfied (or not)." + "is a binary variable equal to 1 when the constraint is satisfied, and equal to 0 when not. Adding a constraint to a model ensures that it is always satisfied. \n", + "With logical constraints, one can use the truth value of a constraint _inside_ the model, allowing to choose dynamically whether a constraint is to be satisfied (or not)." ] }, { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -71,20 +66,14 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "## Use decision optimization" ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Step 1: Import the library\n", "\n", @@ -93,10 +82,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 1, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -116,8 +103,6 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ @@ -133,7 +118,9 @@ "source": [ "#### Discrete linear constraint\n", "\n", - "A discrete linear constraint is built from discrete coefficients and discrete variables, taht is variables with type `integer` or `binary`. For example, assuming x and y are integer variables:\n", + "A discrete linear constraint is built from discrete coefficients and discrete variables, that is variables with type `integer` or `binary`. \n", + "\n", + "For example, assuming x and y are integer variables:\n", "\n", " - `2x+3y == 1` is discrete\n", " - `x+y = 3.14` is not (because of 3.14)\n", @@ -146,21 +133,27 @@ "source": [ "#### The truth value of an added constraint is always 1\n", "\n", - "The truth value of a constraint is accessed by the `status_var` property. This varianle is aplain Docplex decision variable that can be used anywhere a variable can. However, the value of the truth value variable and the constraint are linked, both ways:\n", + "The truth value of a linear constraint is accessed by the `status_var` property. This property returns a binary which can be used anywhere a variable can. However, the value of the truth value variable and the constraint are linked, both ways:\n", "\n", " - a constraint is satisfied if and only if its truth value variable equals 1\n", " - a constraint is _not_ satisfied if and only if its truth value variable equals 0.\n", "\n", - "In this toy model,we show that the truth value of a constraint which has been added to a model is always equal to 1." + "In the following small model, we show that the truth value of a constraint which has been added to a model is always equal to 1." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "the truth value of [ix+iy <= 3] is 1.0\n" + ] + } + ], "source": [ "from docplex.mp.model import Model\n", "\n", @@ -168,6 +161,7 @@ "x = m1.integer_var(name='ix')\n", "y = m1.integer_var(name='iy')\n", "ct = m1.add(x + y <= 3)\n", + "# acces the truth value of a linear constraint\n", "ct_truth = ct.status_var\n", "m1.maximize(x+y)\n", "assert m1.solve()\n", @@ -178,22 +172,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### The truth value of a constraint not added to a model is undefined\n", + "#### The truth value of a constraint not added to a model is free\n", "\n", - "A constraint that is not added to a model, has no effect. Its truth value is undefined: it can be either 1 or 0.\n", + "A constraint that is not added to a model, has no effect. Its truth value is free: it can be either 1 or 0.\n", "\n", "In the following example, both `x` and `y` are set to their upper bound, so that the constraint is not satisfied; hence the truth value is 0." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 8\n", + " \"ix\"=4\n", + " \"iy\"=4\n", + "the truth value of [ix+iy <= 3] is 0\n" + ] + } + ], "source": [ - "m2 = Model()\n", + "m2 = Model(name='logical2')\n", "x = m2.integer_var(name='ix', ub=4)\n", "y = m2.integer_var(name='iy', ub=4)\n", "ct = (x + y <= 3)\n", @@ -210,9 +213,18 @@ "source": [ "#### Using constraint truth values in modeling\n", "\n", - "A constraint's truth value is actually a plain DOcplex decision variable, and as such, can be used with comparison operators and arithmetic operators.\n", + "We have learned about the truth value variable of linear constraints, but there's more.\n", + "Linear constraints can be freely used in _expressions_: Docplex will then substitute the constraint's truth value \n", + "variable in the expression. \n", + "\n", "Let's experiment again with a toy model: in this model,\n", - "we state that the truth value of `y == 4` is less than the truth value of `x ==3`.\n", + "we want to express that when `x ==3` is false, then `y ==4` must also be false.\n", + "To express this, it suffices to say that the truth value of `y == 4` is less than or equal \n", + "to the truth value of `x ==3`. When `x==3` is false, is truthe value is 0, hence the truth value of `y==4` is also zero, and `y` cannot be equal to 4.\n", + "\n", + "However, as shown in the model below, it is not necessary to use the `status_var` propert: using\n", + "the constraints in a comparison expression works fine.\n", + "\n", "As we maximize y, y has value 4 in the optimal solution (it is the upper bound), and consequently the constraint `ct_y4` is satisfied. From the inequality between truth values,\n", "it follows that the truth value of `ct_x2` equals 1 and x is equal to 2.\n", "\n", @@ -221,20 +233,30 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iy: 4\n", + " \"ix\"=2\n", + " \"iy\"=4\n" + ] + } + ], "source": [ - "m3 = Model()\n", + "m3 = Model(name='logical3')\n", "x = m3.integer_var(name='ix', ub=4)\n", "y = m3.integer_var(name='iy', ub=4)\n", "ct_x2 = (x == 2)\n", "ct_y4 = (y == 4)\n", + "# use constraints in comparison\n", "m3.add( ct_y4 <= ct_x2 )\n", "m3.maximize(y)\n", "assert m3.solve()\n", + "# expected solution x==2, and y==4.\n", "m3.print_solution()" ] }, @@ -242,28 +264,38 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Constraint truth values can be used with arithmetic operators, just as variables can. In th enext model, we express a more complex constraint:\n", + "Constraint truth values can be used with arithmetic operators, just as variables can. In the next model, we express a (slightly) more complex constraint:\n", + "\n", "- either x is equal to 3, _or_ both y and z are equal to 5\n", "\n", - "Let's see how we can express this easilty with truth values:" + "Let's see how we can express this easily with truth values:" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 12\n", + " \"ix\"=2\n", + " \"iy\"=5\n", + " \"iz\"=5\n" + ] + } + ], "source": [ - "m31 = Model(name='m31')\n", + "m31 = Model(name='logical31')\n", "x = m31.integer_var(name='ix', ub=4)\n", "y = m31.integer_var(name='iy', ub=10)\n", "z = m31.integer_var(name='iz', ub=10)\n", "ct_x2 = (x == 3)\n", "ct_y5 = (y == 5)\n", "ct_z5 = (z == 5)\n", - "#either ct-x2 is true or -both- ct_y5 and ct_z5 mus be true\n", + "#either ct_x2 is true or -both- ct_y5 and ct_z5 must be true\n", "m31.add( 2 * ct_x2 + (ct_y5 + ct_z5) == 2)\n", "# force x to be less than 2: it cannot be equal to 3!\n", "m31.add(x <= 2)\n", @@ -286,13 +318,29 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 903\n", + " \"x1\"=3\n", + " \"x2\"=100\n", + " \"x3\"=100\n", + " \"x4\"=100\n", + " \"x5\"=100\n", + " \"x6\"=100\n", + " \"x7\"=100\n", + " \"x8\"=100\n", + " \"x9\"=100\n", + " \"x10\"=100\n" + ] + } + ], "source": [ - "m4 = Model()\n", + "m4 = Model(name='logical4')\n", "xs = m4.integer_var_list(10, ub=100)\n", "cts = [xi==3 for xi in xs]\n", "m4.add( m4.sum(cts) == 1)\n", @@ -313,11 +361,27 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 902\n", + " \"x1\"=3\n", + " \"x2\"=100\n", + " \"x3\"=100\n", + " \"x4\"=100\n", + " \"x5\"=100\n", + " \"x6\"=100\n", + " \"x7\"=100\n", + " \"x8\"=100\n", + " \"x9\"=100\n", + " \"x10\"=100\n" + ] + } + ], "source": [ "preference = m4.dot(cts, (k+1 for k in range(len(xs))))\n", "# we prefer lower indices for satisfying the x==3 constraint\n", @@ -338,9 +402,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Using truth values to state 'not equals' constraints.\n", + "#### Using truth values to negate a constraint\n", "\n", - "Truth values can be used to express elegantly 'not equal' constraints, by forcing the truth value of an equality constraint to 0.\n", + "Truth values can be used to negate a complex constraint, by forcing its truth value to be equal to 0.\n", "\n", "In the next model, we illustrate how an equality constraint can be negated by forcing its truth value to zero. This negation forbids y to be equal to 4, as it would be without this negation.\n", "Finally, the objective is 7 instead of 8." @@ -348,33 +412,45 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 6\n", + " \"ix\"=2\n", + " \"iy\"=4\n", + "objective: 8\n", + " \"ix\"=4\n", + " \"iy\"=4\n" + ] + } + ], "source": [ - "m5 = Model()\n", + "m5 = Model(name='logical5')\n", "x = m5.integer_var(name='ix', ub=4)\n", "y = m5.integer_var(name='iy', ub=4)\n", "# this is the equality constraint we want to negate\n", - "ct_y4 = (y == 4)\n", + "ct_xy7 = (y + x >= 7)\n", "# forcing truth value to zero means the constraint is not satisfied.\n", - "negation = m5.add( ct_y4 == 0)\n", - "# maximize x+y should yield both variables to 4, but y cannot be equal to 4\n", - "# as such we expect y to be equal to 3\n", + "# note how we use a constraint in an expression\n", + "negation = m5.add( ct_xy7 == 0)\n", + "# maximize x+y should yield both variables to 4, but x+y cannot be greater than 7\n", "m5.maximize(x + y)\n", "assert m5.solve()\n", "m5.print_solution()\n", - "# expecting 7 as objective, not 8\n", - "assert m5.objective_value == 7\n", + "# expecting 6 as objective, not 8\n", + "assert m5.objective_value == 6\n", "\n", "# now remove the negation\n", "m5.remove_constraint(negation)\n", "# and solve again\n", "assert m5.solve()\n", "# the objective is 8 as expected: both x and y are equal to 4\n", - "assert m5.objective_value == 8" + "assert m5.objective_value == 8\n", + "m5.print_solution()" ] }, { @@ -384,7 +460,53 @@ "#### Summary\n", "\n", "We have seen that linear constraints have an associated binary variable, its _truth value_, whose value is linked to whether or not the constraint is satisfied. \n", - "Moreover, this llink enables to express 'not equals' constraints.# now remove netation" + "\n", + "second, linear constraints can be freely mixed with variables in expression to express _meta-constraints_ that is, constraints\n", + "about constraints. As an example, we have shown how to use truth values to negate constraints." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Note: the `!=` (not_equals) operator\n", + "\n", + "Since version 2.9, Docplex provides a 'not_equal' operator, between discrete expressions. Of course, this is implemented using truth values, but the operator provides a convenient way to express this constraint." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 5\n", + " \"ix\"=2\n", + " \"iy\"=3\n" + ] + } + ], + "source": [ + "m6 = Model(name='logical6')\n", + "x = m6.integer_var(name='ix', ub=4)\n", + "y = m6.integer_var(name='iy', ub=4)\n", + "# this is the equality constraint we want to negate\n", + "m6.add(x +1 <= y)\n", + "m6.add(x != 3)\n", + "m6.add(y != 4)\n", + "# forcing truth value to zero means the constraint is not satisfied.\n", + "# note how we use a constraint in an expression\n", + "m6.add(x+y <= 7)\n", + "# maximize x+y should yield both variables to 4, \n", + "# but here: x < y, y cannot be 4 thus x cannot be 3 either so we get x=2, y=3\n", + "m6.maximize(x + y)\n", + "assert m6.solve()\n", + "m6.print_solution()\n", + "# expecting 5 as objective, not 8\n", + "assert m6.objective_value == 5\n" ] }, { @@ -397,49 +519,231 @@ "\n", "However, in some cases, it can be useful to relate the status of a constraint to an _existing_ binary variable. This is the purpose of equivalence constraints.\n", "\n", - "An equiavelnec constraints relates an existing binary variable to the status of a discrete linear constraints. The syntax is:\n", + "An equivalence constraint relates an existing binary variable to the status of a discrete linear constraints, in both directions. The syntax is:\n", "\n", " `Model.add_equivalence(bvar, linear_ct, active_value, name)`\n", " \n", " - `bvar` is the existing binary variable\n", " - `linear-ct` is a discrete linear constraint\n", " - `active_value` can take values 1 or 0 (the default is 1)\n", - " - `name` is an optional string to name the equivalence." + " - `name` is an optional string to name the equivalence.\n", + " \n", + "If the binary variable `bvar` equals 1, then the constraint is satisfied. Conversely, if the constraint is satisfied, the binary variable is set to 1." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 7\n", + " i_0=5\n", + " i_1=5\n", + " i_2=5\n", + " i_3=5\n", + " i_4=5\n", + " i_5=5\n", + " i_6=5\n", + " j_0=7\n", + " j_1=7\n", + " j_2=7\n", + " j_3=7\n", + " j_4=7\n", + " j_5=7\n", + " j_6=7\n", + " b_0=1\n", + " b_1=1\n", + " b_2=1\n", + " b_3=1\n", + " b_4=1\n", + " b_5=1\n", + " b_6=1\n" + ] + } + ], "source": [ - "m6 = Model(name='m6')\n", + "m7 = Model(name='logical7')\n", "size = 7\n", - "il = m6.integer_var_list(size, name='i', ub=10)\n", - "jl = m6.integer_var_list(size, name='j', ub=10)\n", - "bl = m6.binary_var_list(size, name='b')\n", + "il = m7.integer_var_list(size, name='i', ub=10)\n", + "jl = m7.integer_var_list(size, name='j', ub=10)\n", + "bl = m7.binary_var_list(size, name='b')\n", "for k in range(size):\n", " # for each i, relate bl_k to il_k==5 *and* jl_k == 7\n", - " m6.add_equivalence(bl[k], il[k] == 5)\n", - " m6.add_equivalence(bl[k], jl[k] == 7)\n", + " m7.add_equivalence(bl[k], il[k] == 5)\n", + " m7.add_equivalence(bl[k], jl[k] == 7)\n", "# now maximize sum of bs\n", - "m6.maximize(m6.sum(bl))\n", - "assert m6.solve()\n", - "m6.print_solution()\n", - "\n" + "m7.maximize(m7.sum(bl))\n", + "assert m7.solve()\n", + "m7.print_solution()" ] }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, + "source": [ + "### Step 4: Learn about indicator constraints\n", + "\n", + "The equivalence constraint decsribed in the previous section links the value of an existing binary variable to the satisfaction of a linear constraint. In certain cases, it is sufficient to link from an existing binary variable to the constraint, but not the other way. This is what _indicator_ constraints do.\n", + "\n", + "The syntax is very similar to equivalence:\n", + "\n", + " `Model.add_indicator(bvar, linear_ct, active_value=1, name=None)`\n", + " \n", + " - `bvar` is the existing binary variable\n", + " - `linear-ct` is a discrete linear constraint\n", + " - `active_value` can take values 1 or 0 (the default is 1)\n", + " - `name` is an optional string to name the indicator.\n", + " \n", + " The indicator constraint works as follows: if the binary variable is set to 1, the constraint is satified; if the binary variable is set to 0, anything can happen.\n", + " \n", + " One noteworty difference between indicators and equivalences is that, for indicators, the linear constraint need not be discrete." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following small model, we first solve without the indicator: both b and x are set to their upper bound, and the final objective is 200.\n", + "\n", + "Then we add an indicator sttaing that when b equals1, then x must be less than 3.14; the resulting objective is 103.14, as b is set to 1, which trigger the `x <= 31.4` constraint.\n", + "\n", + "Note that the right-hand side constraint is _not_ discrete (because of 3.14)." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 200.000\n", + " x=100.000\n", + " b=1\n", + "objective: 103.140\n", + " x=3.140\n", + " b=1\n" + ] + } + ], + "source": [ + "m8 = Model(name='logical8')\n", + "x = m8.continuous_var(name='x', ub=100)\n", + "b = m8.binary_var(name='b')\n", + "\n", + "m8.maximize(100*b +x)\n", + "assert m8.solve()\n", + "assert m8.objective_value == 200\n", + "m8.print_solution()\n", + "ind_pi = m8.add_indicator(b, x <= 3.14)\n", + "assert m8.solve()\n", + "assert m8.objective_value <= 104\n", + "m8.print_solution()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 5: Learn about if-then\n", + "\n", + "In this section we explore the `Model.add_if_then` construct which links the truth value of two constraints:\n", + "`Model.add_if_then(if_ct, then_ct)` ensures that, when constraint `if_ct` is satisfied, then `then_ct` is also satisfied.\n", + "When `if_ct` is not satisfied, `then_ct` is free to be satsfied or not.\n", + "\n", + "The syntax is:\n", + "\n", + " `Model.add_if_then(if_ct, then_ct, negate=False)`\n", + " \n", + " - `if_ct` is a discrete linear constraint\n", + " - `then_ct` is any linear constraint (not necessarily discrete),\n", + " - `negate` is an optional flag to reverse the logic, that is satisfy `then_ct` if `if_ct` is not (more on this later)\n", + " \n", + " As for indicators, the `then_ct` need not be discrete.\n", + " \n", + " `Model.add_if_then(if_ct, then_ct)` is roughly equivalent to `Model.add_indicator(if_ct.status_var, then_ct)`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 2403.140\n", + " \"x\"=3.140\n", + " \"iy\"=11\n", + " \"iz\"=13\n" + ] + } + ], + "source": [ + "m9 = Model(name='logical9')\n", + "x = m9.continuous_var(name='x', ub=100)\n", + "y = m9.integer_var(name='iy', ub = 11)\n", + "z = m9.integer_var(name='iz', ub = 13)\n", + "\n", + "m9.add_if_then(y+z >= 10, x <= 3.14)\n", + "\n", + "# y and z are puashed to their ub, so x is down to 3.14\n", + "m9.maximize(x + 100*(y + z))\n", + "m9.solve()\n", + "m9.print_solution()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this second variant, the objective coefficient for `(y+z)` is 2 instead of 100, so `x` domines the objective, and reache sits upper bound, while (y+z) must be less than 9, which is what we observe." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "objective: 118.000\n", + " \"x\"=100.000\n", + " \"iy\"=9\n" + ] + } + ], + "source": [ + "# y and z are pushed to their ub, so x is down to 3.14\n", + "m9.maximize(x + 2 *(y + z))\n", + "m9.solve()\n", + "m9.print_solution()\n", + "\n", + "assert abs(m9.objective_value - 118) <= 1e-2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ "## Summary\n", "\n", + "We have seen that linear constraints have an associated binary variable, its _truth value_, whose value is linked to whether or not the constraint is satisfied. \n", + "\n", + "second, linear constraints can be freely mixed with variables in expression to express _meta-constraints_ that is, constraints\n", + "about constraints. As an example, we have shown how to use truth values to negate constraints.\n", + "\n", + "In addition, we have learned to use equivalence, indicator and if_then constraints.\n", + "\n", "\n", "You learned how to set up and use the IBM Decision Optimization CPLEX Modeling for Python to formulate a Mathematical Programming model with logical constraints." ] @@ -447,50 +751,52 @@ { "cell_type": "markdown", "metadata": { - "deletable": true, - "editable": true, "render": true }, "source": [ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud)\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": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. Sample Materials." + "Copyright © 2017-2019 IBM. Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "anaconda-cloud": {}, "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "531_36", "language": "python", - "name": "python2" + "name": "531_36" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.8" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/marketing_campaign.ipynb b/examples/mp/jupyter/marketing_campaign.ipynb index 9f438e1..702c816 100644 --- a/examples/mp/jupyter/marketing_campaign.ipynb +++ b/examples/mp/jupyter/marketing_campaign.ipynb @@ -10,15 +10,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the [Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/).\n", - "\n", - ">Running the sample requires the installation of\n", - " [CPLEX Optimization studio](https://www.ibm.com/products/ilog-cplex-optimization-studio)\n", - " (Commercial or free \n", - " [CPLEX Community edition](https://www.ibm.com/account/reg/us-en/signup?formid=urx-20028>`)).\n", - " This sample automatically installs *CPLEX CE* if needed.\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud).\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", @@ -28,12 +23,12 @@ "* [Prepare the data](#Prepare-the-data)\n", "* [Use decision optimization](#Use-IBM-Decision-Optimization-CPLEX-Modeling-for-Python)\n", " * [Step 1: Import the library](#Step-1:-Import-the-library)\n", - " - [Step 2: Set up the prescriptive model](#Step-3:-Set-up-the-prescriptive-model)\n", + " - [Step 2: Set up the prescriptive model](#Step-2:-Set-up-the-prescriptive-model)\n", " * [Define the decision variables](#Define-the-decision-variables)\n", " * [Set up the constraints](#Set-up-the-constraints)\n", " * [Express the objective](#Express-the-objective)\n", - " * [Solve with Decision Optimization](#Solve-with-Decision-Optimization)\n", - " * [Step 3: Analyze the solution and run an example analysis](#Step-4:-Analyze-the-solution)\n" + " * [Solve the model](#Solve-the-model)\n", + " * [Step 3: Analyze the solution and run an example analysis](#Step-3:-Analyze-the-solution)\n" ] }, { @@ -95,9 +90,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", @@ -141,9 +134,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try: # Python 2\n", @@ -163,9 +154,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "CSS = \"\"\"\n", @@ -211,9 +200,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML\n", @@ -247,9 +234,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -269,9 +254,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -291,9 +274,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -314,9 +295,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try: # Python 2\n", @@ -346,9 +325,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Only 1 product is offered to each customer \n", @@ -391,9 +368,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "mdl.maximize(\n", @@ -421,9 +396,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "s = mdl.solve()\n", @@ -442,9 +415,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "report = [(channels.get_value(index=c, col=\"name\"), products[p], names[offers.get_value(o, \"customerid\")]) \n", @@ -471,9 +442,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display(report_bd[report_bd['channel'] == \"seminar\"].drop('channel',1))" @@ -499,7 +468,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -507,31 +476,38 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "celltoolbar": "Dashboard", "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/mining_pandas.ipynb b/examples/mp/jupyter/mining_pandas.ipynb index 7d313d0..dcb44c9 100644 --- a/examples/mp/jupyter/mining_pandas.ipynb +++ b/examples/mp/jupyter/mining_pandas.ipynb @@ -10,13 +10,10 @@ "\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", - ">Running the sample requires the installation of\n", - " [CPLEX Optimization studio](https://www.ibm.com/products/ilog-cplex-optimization-studio)\n", - " (Commercial or free \n", - " [CPLEX Community edition](https://www.ibm.com/account/reg/us-en/signup?formid=urx-20028>`)).\n", - " This sample automatically installs *CPLEX CE* if needed.\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", @@ -109,9 +106,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import pip\n", @@ -142,9 +137,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -164,9 +157,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -188,9 +179,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# If needed, install the module pandas prior to executing this cell\n", @@ -201,9 +190,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_mines = DataFrame({\"royalties\": [ 5 , 4, 4, 5 ],\n", @@ -227,9 +214,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "blend_qualities = Series([0.9, 0.8, 1.2, 0.6, 1.0])\n", @@ -255,9 +240,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# global data\n", @@ -284,9 +267,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -305,9 +286,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -339,9 +318,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# auxiliary data: ranges\n", @@ -369,9 +346,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Organize all decision variables in a DataFrame indexed by 'range_mines' and 'range_years'\n", @@ -395,9 +370,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mm.add_constraints(t.work <= t.open for t in df_decision_vars.itertuples())\n", @@ -421,9 +394,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Once closed, a mine stays closed\n", @@ -457,9 +428,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Maximum number of worked mines each year\n", @@ -491,9 +460,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Display rows of 'df_decision_vars' joined with 'df_mines.max_extract' Series for first two mines\n", @@ -510,9 +477,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# quantity extracted is limited\n", @@ -541,9 +506,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# blend variables\n", @@ -567,9 +530,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Quality requirement on blended ore\n", @@ -592,9 +553,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "actualization = 1.0 - discount_rate\n", @@ -619,9 +578,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "expected_revenue = blend_price * mm.dot(blend_vars, s_discounts)\n", @@ -648,9 +605,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_royalties_data = df_decision_vars.join(df_mines.royalties).join(s_discounts)\n", @@ -670,9 +625,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "total_royalties = mm.dot(df_royalties_data.open, df_royalties_data.disc_royalties)\n", @@ -692,9 +645,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mm.maximize(expected_revenue - total_royalties)" @@ -710,9 +661,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mm.print_information()\n", @@ -738,9 +687,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mine_labels = [(\"mine%d\" % (m+1)) for m in range_mines]\n", @@ -773,9 +720,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# import matplotlib library for visualization\n", @@ -803,9 +748,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# a list of (mine, year) tuples on which work is not possible.\n", @@ -839,9 +782,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# build a new, empty solution\n", @@ -867,7 +808,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -890,9 +830,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Add a column to DataFrame containing 'ore' decision variables value and create a pivot table by (years, mines)\n", @@ -932,7 +870,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -940,30 +878,37 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/nurses_pandas-multi_objective.ipynb b/examples/mp/jupyter/nurses_pandas-multi_objective.ipynb index 875a574..dd014b9 100644 --- a/examples/mp/jupyter/nurses_pandas-multi_objective.ipynb +++ b/examples/mp/jupyter/nurses_pandas-multi_objective.ipynb @@ -10,11 +10,10 @@ "\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 an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -132,9 +131,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": false - }, + "metadata": {}, "source": [ "### Step 2: Model the data\n", "\n", @@ -254,9 +251,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# This notebook requires pandas to work\n", @@ -332,9 +327,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_shifts" @@ -356,9 +349,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "days = [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\", \"saturday\", \"sunday\"]\n", @@ -386,9 +377,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_shifts[\"wstart\"] = df_shifts.start_time + 24 * df_shifts.dow" @@ -411,9 +400,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# an auxiliary function to calculate absolute end time of a shift\n", @@ -460,9 +447,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# also compute minimum demand in nurse-hours\n", @@ -482,9 +467,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -505,9 +488,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -560,9 +541,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Organize decision variables in a DataFrame\n", @@ -594,9 +573,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a Data Frame representing a list of shifts sorted by wstart and duration.\n", @@ -617,9 +594,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "number_of_incompatible_shift_constraints = 0\n", @@ -653,9 +628,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Add 'day of week' column to vacations Data Frame\n", @@ -676,9 +649,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for forbidden_assignment in df_vacation_forbidden_assignments.itertuples():\n", @@ -702,9 +673,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Join 'df_assignment' Data Frame twice, based on associations to get corresponding decision variables pairs for all shifts\n", @@ -752,9 +721,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Join assignment Data Frame twice, based on incompatibilities Data Frame to get corresponding decision variables pairs\n", @@ -804,9 +771,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# auxiliary function to create worktime variable from a row\n", @@ -834,9 +799,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Use pandas' groupby operation to enforce constraint calculating worktime for each nurse as the sum of all assigned\n", @@ -861,9 +824,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# we use pandas' apply() method to set an upper bound on all worktime variables.\n", @@ -917,9 +878,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# again leverage pandas to create a series of expressions: costs of each nurse\n", @@ -944,9 +903,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.minimize(total_salary_cost)\n", @@ -965,9 +922,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Set Cplex mipgap to 1e-5 to enforce precision to be of the order of a unit (objective value magnitude is ~1e+5).\n", @@ -992,9 +947,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a pandas Series containing actual shift assignment decision variables value\n", @@ -1022,9 +975,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "s_demand = df_shifts.min_req * df_shifts.duration\n", @@ -1043,9 +994,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# a pandas series of worktimes solution values\n", @@ -1073,9 +1022,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", @@ -1102,9 +1049,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# a pandas series of #shifts worked\n", @@ -1128,9 +1073,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "avg_worked = df_shifts[\"min_req\"].sum() / float(len(all_nurses))\n", @@ -1160,9 +1103,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# add two extra variables per nurse: deviations above and below average\n", @@ -1206,9 +1147,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# finally, define kpis for over and under average quantities\n", @@ -1250,9 +1189,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "sol2 = mdl.solve(log_output=True) # solve again and get a new solution\n", @@ -1285,9 +1222,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.constants import ObjectiveSense\n", @@ -1305,9 +1240,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "sol3 = mdl.solve(log_output=True) # solve the multi-objective problem\n", @@ -1332,9 +1265,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.set_multi_objective(ObjectiveSense.Minimize, [total_overw, total_underw, total_salary_cost], priorities=[1, 1, 0])\n", @@ -1362,7 +1293,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -1370,29 +1301,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/nurses_pandas.ipynb b/examples/mp/jupyter/nurses_pandas.ipynb index 568b60b..2e8d964 100644 --- a/examples/mp/jupyter/nurses_pandas.ipynb +++ b/examples/mp/jupyter/nurses_pandas.ipynb @@ -10,11 +10,10 @@ "\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 an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -85,9 +84,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import pip\n", @@ -118,9 +115,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -132,9 +127,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": false - }, + "metadata": {}, "source": [ "### Step 2: Model the data\n", "\n", @@ -164,9 +157,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "CSS = \"\"\"\n", @@ -212,9 +203,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML\n", @@ -226,9 +215,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -240,9 +227,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -254,9 +239,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# This notebook requires pandas to work\n", @@ -310,9 +293,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# maximum work time (in hours)\n", @@ -332,9 +313,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_shifts" @@ -356,9 +335,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "days = [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\", \"saturday\", \"sunday\"]\n", @@ -386,9 +363,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_shifts[\"wstart\"] = df_shifts.start_time + 24 * df_shifts.dow" @@ -411,9 +386,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# an auxiliary function to calculate absolute end time of a shift\n", @@ -437,9 +410,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df_shifts[\"duration\"] = df_shifts.wend - df_shifts.wstart" @@ -460,9 +431,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# also compute minimum demand in nurse-hours\n", @@ -482,9 +451,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -505,9 +472,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -528,9 +493,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# first global collections to iterate upon\n", @@ -560,9 +523,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Organize decision variables in a DataFrame\n", @@ -594,9 +555,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a Data Frame representing a list of shifts sorted by wstart and duration.\n", @@ -617,9 +576,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "number_of_incompatible_shift_constraints = 0\n", @@ -653,9 +610,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Add 'day of week' column to vacations Data Frame\n", @@ -676,9 +631,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "for forbidden_assignment in df_vacation_forbidden_assignments.itertuples():\n", @@ -702,9 +655,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Join 'df_assignment' Data Frame twice, based on associations to get corresponding decision variables pairs for all shifts\n", @@ -728,9 +679,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "for preferred_assign in df_preferred_assign.itertuples():\n", @@ -752,9 +701,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Join assignment Data Frame twice, based on incompatibilities Data Frame to get corresponding decision variables pairs\n", @@ -777,9 +724,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "for incompatible_assign in df_incompatible_assign.itertuples():\n", @@ -804,9 +749,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# auxiliary function to create worktime variable from a row\n", @@ -834,9 +777,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Use pandas' groupby operation to enforce constraint calculating worktime for each nurse as the sum of all assigned\n", @@ -861,9 +802,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# we use pandas' apply() method to set an upper bound on all worktime variables.\n", @@ -891,9 +830,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Use pandas' groupby operation to enforce minimum requirement constraint for each shift\n", @@ -917,9 +854,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# again leverage pandas to create a series of expressions: costs of each nurse\n", @@ -944,9 +879,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.minimize(total_salary_cost)\n", @@ -965,9 +898,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Set Cplex mipgap to 1e-5 to enforce precision to be of the order of a unit (objective value magnitude is ~1e+5).\n", @@ -982,7 +913,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Step 6: Investigate the solution and then run an example analysis\n", + "### Step 5: Investigate the solution and then run an example analysis\n", "\n", "We take advantage of *pandas* to analyze the results. First we store the solution values of the assignment variables into a new *pandas* Series.\n", "\n", @@ -992,9 +923,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a pandas Series containing actual shift assignment decision variables value\n", @@ -1022,9 +951,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "s_demand = df_shifts.min_req * df_shifts.duration\n", @@ -1043,9 +970,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# a pandas series of worktimes solution values\n", @@ -1073,9 +998,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", @@ -1102,9 +1025,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# a pandas series of #shifts worked\n", @@ -1128,9 +1049,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "avg_worked = df_shifts[\"min_req\"].sum() / float(len(all_nurses))\n", @@ -1160,9 +1079,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# add two extra variables per nurse: deviations above and below average\n", @@ -1181,9 +1098,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Use the pandas groupby operation to enforce the constraint calculating number of worked shifts for each nurse\n", @@ -1206,9 +1121,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# finally, define kpis for over and under average quantities\n", @@ -1230,9 +1143,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "mdl.minimize(total_salary_cost + total_overw + total_underw) # incorporate over_worked and under_worked in objective" @@ -1250,9 +1161,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "sol2 = mdl.solve(log_output=True) # solve again and get a new solution\n", @@ -1272,9 +1181,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a pandas Series containing actual shift assignment decision variables value\n", @@ -1305,9 +1212,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_res2[\"worked\"].plot(kind=\"hist\", color=\"gold\", xlim=(3,8))" @@ -1337,9 +1242,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.minimize(total_overw + total_underw)\n", @@ -1362,9 +1265,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a pandas Series containing actual shift assignment decision variables value\n", @@ -1398,7 +1299,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -1406,29 +1307,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/nurses_scheduling.ipynb b/examples/mp/jupyter/nurses_scheduling.ipynb index a6c09c5..3896030 100644 --- a/examples/mp/jupyter/nurses_scheduling.ipynb +++ b/examples/mp/jupyter/nurses_scheduling.ipynb @@ -10,11 +10,10 @@ "\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 an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -91,10 +90,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 1, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -136,10 +133,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "from enum import Enum\n", @@ -238,10 +233,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 3, + "metadata": {}, "outputs": [], "source": [ "CSS = \"\"\"\n", @@ -286,10 +279,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 4, + "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML\n", @@ -300,10 +291,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 5, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -314,11 +303,305 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "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", + " \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", + " \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", + " \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", + " \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", + " \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", + " \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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nameseniorityqualificationpay_rate
0Anne11125
1Bethanie4528
2Betsy2217
3Cathy2217
4Cecilia9538
5Chris11438
6Cindy5221
7David1215
8Debbie7224
9Dee3321
10Gloria8225
11Isabelle3116
12Jane3423
13Janelle4322
14Janice2217
15Jemma2422
16Joan5324
17Joyce8329
18Jude4322
19Julie6222
20Juliet7431
21Kate5324
22Nancy8432
23Nathalie9538
24Nicole0214
25Patricia1113
26Patrick6119
27Roberta3526
28Suzanne5118
29Vickie7120
30Wendie5221
31Zoe8329
\n", + "
" + ], + "text/plain": [ + " name seniority qualification pay_rate\n", + "0 Anne 11 1 25\n", + "1 Bethanie 4 5 28\n", + "2 Betsy 2 2 17\n", + "3 Cathy 2 2 17\n", + "4 Cecilia 9 5 38\n", + "5 Chris 11 4 38\n", + "6 Cindy 5 2 21\n", + "7 David 1 2 15\n", + "8 Debbie 7 2 24\n", + "9 Dee 3 3 21\n", + "10 Gloria 8 2 25\n", + "11 Isabelle 3 1 16\n", + "12 Jane 3 4 23\n", + "13 Janelle 4 3 22\n", + "14 Janice 2 2 17\n", + "15 Jemma 2 4 22\n", + "16 Joan 5 3 24\n", + "17 Joyce 8 3 29\n", + "18 Jude 4 3 22\n", + "19 Julie 6 2 22\n", + "20 Juliet 7 4 31\n", + "21 Kate 5 3 24\n", + "22 Nancy 8 4 32\n", + "23 Nathalie 9 5 38\n", + "24 Nicole 0 2 14\n", + "25 Patricia 1 1 13\n", + "26 Patrick 6 1 19\n", + "27 Roberta 3 5 26\n", + "28 Suzanne 5 1 18\n", + "29 Vickie 7 1 20\n", + "30 Wendie 5 2 21\n", + "31 Zoe 8 3 29" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# This notebook requires pandas to work\n", "import pandas as pd\n", @@ -366,10 +649,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 7, + "metadata": {}, "outputs": [], "source": [ "skills = [SkillTable[\"name\"][i] for i in range(len(SkillTable))]\n", @@ -411,11 +692,21 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* system is: Windows 64bit\n", + "* Python version 3.6.8, located at: C:\\python\\anaconda531\\envs\\531_36\\python.exe\n", + "* docplex is present, version is (2, 10, 0)\n", + "* CPLEX library is present, version is 12.9.0.0, located at: C:\\OPTIM\\python\\cplex1290R1\\3.6\\x64_win64\n", + "* pandas is present, version is 0.23.4\n" + ] + } + ], "source": [ "from docplex.mp.environment import Environment\n", "env = Environment()\n", @@ -432,10 +723,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 9, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -457,10 +746,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 10, + "metadata": {}, "outputs": [], "source": [ "# One binary variable for each pair (nurse, shift) equal to 1 if nurse n is assigned to shift s\n", @@ -488,11 +775,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "docplex.mp.LinearConstraint[average](18AverageWorkTime,EQ,NurseWorkTime_Betsy+NurseWorkTime_Cathy+NurseWorkTime_Cindy+NurseWorkTime_Debbie+NurseWorkTime_Dee+NurseWorkTime_Isabelle+NurseWorkTime_Jane+NurseWorkTime_Janelle+NurseWorkTime_Janice+NurseWorkTime_Jemma+NurseWorkTime_Joan+NurseWorkTime_Jude+NurseWorkTime_Julie+NurseWorkTime_Kate+NurseWorkTime_Patrick+NurseWorkTime_Suzanne+NurseWorkTime_Vickie+NurseWorkTime_Wendie)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "mdl.add_constraint(len(nurses) * average_nurse_work_time ==\n", " mdl.sum(nurse_work_time_vars[n] for n in nurses), \"average\")" @@ -507,10 +803,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 12, + "metadata": {}, "outputs": [], "source": [ "for n in nurses:\n", @@ -544,10 +838,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 13, + "metadata": {}, "outputs": [], "source": [ "for vac_nurse_id, vac_day in vacations:\n", @@ -568,11 +860,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# overlapping shifts: 20\n" + ] + } + ], "source": [ "# Post only one constraint per couple(s1, s2)\n", "number_of_overlaps = 0\n", @@ -601,10 +899,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 15, + "metadata": {}, "outputs": [], "source": [ "for s in shifts:\n", @@ -627,10 +923,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 16, + "metadata": {}, "outputs": [], "source": [ "for (dept, skill, required) in skill_requirements:\n", @@ -653,10 +947,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, + "execution_count": 17, + "metadata": {}, "outputs": [], "source": [ "# for each pair of associted nurses, their assignement variables are equal over all shifts.\n", @@ -682,10 +974,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 18, + "metadata": {}, "outputs": [], "source": [ "# For each pair of incompatible nurses, the sum of assigned variables is less than one\n", @@ -717,11 +1007,23 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: nurses\n", + " - number of variables: 793\n", + " - binary=738, integer=0, continuous=55\n", + " - number of constraints: 961\n", + " - linear=961\n", + " - parameters: defaults\n", + " - problem type is: MILP\n" + ] + } + ], "source": [ "total_number_of_assignments = mdl.sum(nurse_assignment_vars[n,s] for n in nurses for s in shifts)\n", "nurse_costs = [nurse_assignment_vars[n, s] * n.pay_rate * shift_activities[s].duration for n in nurses for s in shifts]\n", @@ -754,10 +1056,8 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, + "execution_count": 20, + "metadata": {}, "outputs": [], "source": [ "mdl.minimize(total_salary_cost + total_fairness + total_number_of_assignments)" @@ -773,11 +1073,74 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPXPARAM_Read_DataCheck 1\n", + "CPXPARAM_MIP_Tolerances_MIPGap 1.0000000000000001e-05\n", + "Bound infeasibility column 'NurseWorkTime_Betsy'.\n", + "Presolve time = 0.00 sec. (0.29 ticks)\n", + "\n", + "Root node processing (before b&c):\n", + " Real time = 0.00 sec. (0.45 ticks)\n", + "Parallel b&c, 8 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.00 sec. (0.45 ticks)\n", + "Warning: 55 constraint(s) will not be relaxed (e.g.: average: 18AverageWorkTime == NurseWorkTime_Betsy+NurseWorkTime_Cathy+NurseWorkTime_Cindy+NurseWorkTime_Debbie+NurseWorkTime_Dee+NurseWorkTime_Isabelle+NurseWorkTime_Jane+NurseWorkTime_Janelle+NurseWorkTime_Janice+NurseWorkTime_Jemma+NurseWorkTime_Joan+NurseWorkTime_Jude+NurseWorkTime_Julie+NurseWorkTime_Kate+NurseWorkTime_Patrick+NurseWorkTime_Suzanne+NurseWorkTime_Vickie+NurseWorkTime_Wendie)\n", + "* number of relaxations: 35\n", + " - relaxed: high_req_min_EMER_Mon_08_4, with relaxation: -2.0\n", + " - relaxed: high_req_min_EMER_Mon_18_3, with relaxation: -3.0\n", + " - relaxed: high_req_min_CONS_Mon_08_10, with relaxation: -5.0\n", + " - relaxed: high_req_min_CONS_Mon_12_8, with relaxation: -4.0\n", + " - relaxed: high_req_min_CARD_Mon_08_10, with relaxation: -4.0\n", + " - relaxed: high_req_min_CARD_Mon_12_8, with relaxation: -5.0\n", + " - relaxed: high_req_min_EMER_Tue_08_4, with relaxation: -2.0\n", + " - relaxed: high_req_min_EMER_Tue_18_3, with relaxation: -3.0\n", + " - relaxed: high_req_min_CONS_Tue_08_10, with relaxation: -5.0\n", + " - relaxed: high_req_min_CONS_Tue_12_8, with relaxation: -5.0\n", + " - relaxed: high_req_min_CARD_Tue_18_3, with relaxation: -3.0\n", + " - relaxed: high_req_min_EMER_Wed_12_2, with relaxation: -1.0\n", + " - relaxed: high_req_min_EMER_Wed_18_3, with relaxation: -3.0\n", + " - relaxed: high_req_min_EMER_Thu_18_3, with relaxation: -3.0\n", + " - relaxed: high_req_min_CONS_Thu_08_10, with relaxation: -2.0\n", + " - relaxed: high_req_min_EMER_Fri_02_3, with relaxation: -1.0\n", + " - relaxed: high_req_min_EMER_Fri_18_3, with relaxation: -3.0\n", + " - relaxed: high_req_min_CONS_Fri_08_10, with relaxation: -3.0\n", + " - relaxed: high_req_min_CONS_Fri_12_8, with relaxation: -3.0\n", + " - relaxed: high_req_min_EMER_Sat_02_5, with relaxation: -5.0\n", + " - relaxed: high_req_min_EMER_Sat_12_7, with relaxation: -5.0\n", + " - relaxed: high_req_min_EMER_Sat_20_12, with relaxation: -4.0\n", + " - relaxed: high_req_min_EMER_Sun_02_5, with relaxation: -5.0\n", + " - relaxed: high_req_min_EMER_Sun_12_7, with relaxation: -7.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Mon_18, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Tue_18, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Wed_18, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Thu_02, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Thu_18, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Fri_18, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sat_02, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sat_12, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sat_20, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sun_02, with relaxation: -1.0\n", + " - relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sun_12, with relaxation: -1.0\n", + "* total absolute relaxation: 97.0\n", + "* model nurses solved with objective = 14097.333\n", + "* KPI: Total salary cost = 13940.000\n", + "* KPI: Total number of assignments = 134.000\n", + "* KPI: AverageWorkTime = 37.667\n", + "* KPI: Total over-average worktime = 11.667\n", + "* KPI: Total under-average worktime = 11.667\n", + "* KPI: Total fairness = 23.333\n" + ] + } + ], "source": [ "# Set Cplex mipgap to 1e-5 to enforce precision to be of the order of a unit (objective value magnitude is ~1e+5).\n", "mdl.parameters.mip.tolerances.mipgap = 1e-5\n", @@ -818,11 +1181,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-24" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "min(shift_activities, key=lambda i: shift_activities[i].day_start_time)\n", "min(s.day_start_time for s in shift_activities.values())" @@ -830,11 +1202,22 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "import matplotlib.pyplot as plt\n", "import matplotlib.gridspec as gridspec\n", @@ -942,7 +1325,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -950,29 +1333,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "531_36", "language": "python", - "name": "python2" + "name": "531_36" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.8" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/oil_blending.ipynb b/examples/mp/jupyter/oil_blending.ipynb index 52763e1..2abe1e2 100644 --- a/examples/mp/jupyter/oil_blending.ipynb +++ b/examples/mp/jupyter/oil_blending.ipynb @@ -11,13 +11,10 @@ "\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", - ">Running the sample requires the installation of\n", - " [CPLEX Optimization studio](https://www.ibm.com/products/ilog-cplex-optimization-studio)\n", - " (Commercial or free \n", - " [CPLEX Community edition](https://www.ibm.com/account/reg/us-en/signup?formid=urx-20028>`)).\n", - " This sample automatically installs *CPLEX CE* if needed. \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", @@ -102,9 +99,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -124,9 +119,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -163,9 +156,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", @@ -209,9 +200,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", @@ -233,9 +222,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "CSS = \"\"\"\n", @@ -291,9 +278,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.display import display\n", @@ -316,9 +301,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -339,9 +322,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "blends = mdl.continuous_var_matrix(keys1=nb_oils, keys2=nb_gas, lb=0)" @@ -357,9 +338,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "adverts = mdl.continuous_var_list(nb_gas, lb=0)" @@ -390,9 +369,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# gasoline demand is numpy array field #0\n", @@ -413,9 +390,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints(mdl.sum(blends[o,g] for g in range_gas) <= oil_data[o][0]\n", @@ -434,9 +409,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# minimum octane level\n", @@ -464,9 +437,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# -- maximum global production\n", @@ -493,9 +464,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# KPIs\n", @@ -526,9 +495,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "assert mdl.solve(), \"Solve failed\"\n", @@ -548,9 +515,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "all_kpis = [(kp.name, kp.compute()) for kp in mdl.iter_kpis()]\n", @@ -560,9 +525,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "blend_values = [ [ blends[o,g].solution_value for g in range_gas] for o in range_oil]\n", @@ -582,9 +545,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -608,9 +569,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "display_pie(total_gas_prods, gas_names, colors=[\"green\", \"goldenrod\", \"lightGreen\"],title='Gasoline Total Production')" @@ -629,9 +588,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "sblends = [(gas_names[n], oil_names[o], round(blends[o,n].solution_value)) for n in range_gas for o in range_oil]\n", @@ -642,9 +599,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "f, barplot = plt.subplots(1, figsize=(16,5))\n", @@ -692,9 +647,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print(\"* value of blend[crude2, diesel] is %g\" % blends[1,2])" @@ -719,7 +672,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -727,28 +680,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "celltoolbar": "Dashboard", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, diff --git a/examples/mp/jupyter/pasta_production.ipynb b/examples/mp/jupyter/pasta_production.ipynb index 43c8a21..5454911 100644 --- a/examples/mp/jupyter/pasta_production.ipynb +++ b/examples/mp/jupyter/pasta_production.ipynb @@ -10,11 +10,10 @@ "\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 an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -109,9 +108,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": false - }, + "metadata": {}, "source": [ "### Step 2: Model the data\n", "\n", @@ -162,9 +159,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -185,9 +180,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -227,7 +220,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -254,9 +246,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "total_inside_cost = mdl.sum(inside_vars[p] * p[2] for p in products)\n", @@ -277,9 +267,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.solve()" @@ -296,7 +284,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [], @@ -330,7 +317,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -338,29 +325,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/progress.ipynb b/examples/mp/jupyter/progress.ipynb new file mode 100644 index 0000000..a021e11 --- /dev/null +++ b/examples/mp/jupyter/progress.ipynb @@ -0,0 +1,902 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "render": true + }, + "source": [ + "# Using the Progress Listeners with CPLEX Optimizer\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", + "- [Describe the business problem](#Describe-the-business-problem:--Games-Scheduling-in-the-National-Football-League)\n", + "* [How decision optimization (prescriptive analytics) can help](#How--decision-optimization-can-help)\n", + "* [Use decision optimization](#Use-decision-optimization)\n", + " * [Step 1: Set up the prescriptive 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", + "
\n", + "\n", + "With prescriptive analytics, you can: \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": "markdown", + "metadata": {}, + "source": [ + "## Use decision optimization" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "render": true + }, + "source": [ + "### Step 1: Set up the prescriptive model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We need a scalable MIP model in order to show how to leverage progress listeners in Docplex MP API. \n", + "\n", + "Progress listeners are designed to monitor the progress of complex MIP search in docplex MP, \n", + "that is, linear programs with integer variables.\n", + "\n", + "This model is easily scalable, and thus is appropriate to demonstrate the progress listener API, but any other scalable MIP model would do." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from docplex.mp.model import Model\n", + "\n", + "def build_hearts(r, **kwargs):\n", + " # initialize the model\n", + " mdl = Model('love_hearts_%d' % r, **kwargs)\n", + "\n", + " # the dictionary of decision variables, one variable\n", + " # for each circle with i in (1 .. r) as the row and\n", + " # j in (1 .. i) as the position within the row \n", + " idx = [(i, j) for i in range(1, r + 1) for j in range(1, i + 1)]\n", + " a = mdl.binary_var_dict(idx, name=lambda ij: \"a_%d_%d\" % ij)\n", + "\n", + " # the constraints - enumerate all equilateral triangles\n", + " # and prevent any such triangles being formed by keeping\n", + " # the number of included circles at its vertexes below 3\n", + "\n", + " # for each row except the last\n", + " for i in range(1, r):\n", + " # for each position in this row\n", + " for j in range(1, i + 1):\n", + " # for each triangle of side length (k) with its upper vertex at\n", + " # (i, j) and its sides parallel to those of the overall shape\n", + " for k in range(1, r - i + 1):\n", + " # the sets of 3 points at the same distances clockwise along the\n", + " # sides of these triangles form k equilateral triangles\n", + " for m in range(k):\n", + " u, v, w = (i + m, j), (i + k, j + m), (i + k - m, j + k - m)\n", + " mdl.add(a[u] + a[v] + a[w] <= 2)\n", + "\n", + " mdl.maximize(mdl.sum(a))\n", + " return mdl" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try to build a small instance of the 'hearts' program and print its characteristics." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: love_hearts_5\n", + " - number of variables: 15\n", + " - binary=15, integer=0, continuous=0\n", + " - number of constraints: 35\n", + " - linear=35\n", + " - parameters: defaults\n", + " - problem type is: MILP\n" + ] + } + ], + "source": [ + "m5 = build_hearts(5)\n", + "m5.print_information()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 2: Monitoring CPLEX progress\n", + "\n", + "MIP search can take some time for large (or complex) problems. Setting the `log_output=True` in a solve() lets\n", + "you display the CPLEX log, which provides a lot of information. In certain cases, you might want to take control of what happens at intermediate points in the search, and this is what listeners are designed for.\n", + "\n", + "#### An introduction to progress listeners\n", + "\n", + "Progress listeners are objects, sub-classes of the `docplex.mp.progress.ProgressListener` class. Once a listener has been attached to a model instance (using `Model.add_progress_listener`), it receives method calls from within the CPLEX MIP search. CPLEX code decides when listeners are called, and this baseline logic cannot be changed. \n", + "However, progress listeners let you select certian types of events.\n", + "\n", + "First, we have to import the `docplex.mp.progress` module, which contains everything about progress listeners." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from docplex.mp.progress import *\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Monitoring MIP search progress\n", + "\n", + "The simplest class of listener is the `TextProgressListener`, which prints a message on the stdout each time it is called. Let's see what this does on our small model." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# connect a listener to the model\n", + "m5.add_progress_listener(TextProgressListener())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "render": true + }, + "source": [ + "#### Solve with Decision Optimization\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.\n", + "\n", + "Here, we solve with the ***clean_before_true*** flag set to True, as we want each solve to produce the same output. Without this flag, a second solve on the model would start from the first solve solution, and would not have the same output." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1+: Node=0 Left=1 Best Integer=7.0000, Best Bound=10.0000, gap=42.86%, ItCnt=21 [0.0s]\n", + " 2+: Node=0 Left=1 Best Integer=7.0000, Best Bound=9.3266, gap=33.24%, ItCnt=33 [0.0s]\n", + " 3+: Node=0 Left=1 Best Integer=7.0000, Best Bound=9.1960, gap=31.37%, ItCnt=41 [0.0s]\n", + " 4+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.1960, gap=14.95%, ItCnt=41 [0.0s]\n", + " 5+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.0000, gap=12.50%, ItCnt=50 [0.0s]\n" + ] + } + ], + "source": [ + "m5.solve(clean_before_solve=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The listener prints one line each time it is called by CPLEX code; fromthis we can see that:\n", + "\n", + " - the listener is called several time from the same node (0)\n", + " - the listener is called several time at the same iteration (ItCnt=42)\n", + " - the listener is called several times with the same objective 7.0\n", + " \n", + "In each line, the '+' indicates that an intermediate solution is available at the time of the call. In this case, an intermediate solution was available at each call, but this is not always the case.\n", + "Looking closer, we also see that the listener reacts to events which improve either the objective or the best bound.\n", + "This is due to the value of the _clock_ attribute of the listener\n", + "\n", + "#### Listener clocks\n", + "\n", + "Clocks are value sof the enumerated type `docplex.mp.progress.ProgressClock`, which defines types of events to listen to. Every listener has a clock, the default being `ProgressClock.Gap`, which reacts when an event satisfies the following conditions:\n", + "\n", + " - an intermediate solution is available \n", + " - either the objective has improved _or_ the best bound has improved\n", + " \n", + "Let's check this on our model:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "listener #1 has type 'TextProgressListener', clock=ProgressClock.Gap\n" + ] + } + ], + "source": [ + "for l, listener in enumerate(m5.iter_progress_listeners(), start=1):\n", + " print(\"listener #{0} has type '{1}', clock={2}\".format(l, listener.__class__.__name__, listener.clock))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's experiment with a text progress listener listening to the `All` clock, that is the baseline clock that reacts to all calls from CPLEX. To do so, we first clear all progress listeners and add a new one.\n", + "\n", + "Note the constructor also accepts strings, interpreted as clock name." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1+: Node=0 Left=1 Best Integer=7.0000, Best Bound=10.0000, gap=42.86%, ItCnt=21 [0.0s]\n", + " 2+: Node=0 Left=1 Best Integer=7.0000, Best Bound=10.0000, gap=42.86%, ItCnt=21 [0.0s]\n", + " 3+: Node=0 Left=1 Best Integer=7.0000, Best Bound=10.0000, gap=42.86%, ItCnt=33 [0.0s]\n", + " 4+: Node=0 Left=1 Best Integer=7.0000, Best Bound=9.3266, gap=33.24%, ItCnt=33 [0.0s]\n", + " 5+: Node=0 Left=1 Best Integer=7.0000, Best Bound=9.3266, gap=33.24%, ItCnt=41 [0.0s]\n", + " 6+: Node=0 Left=1 Best Integer=7.0000, Best Bound=9.1960, gap=31.37%, ItCnt=41 [0.0s]\n", + " 7+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.1960, gap=14.95%, ItCnt=41 [0.0s]\n", + " 8+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.1960, gap=14.95%, ItCnt=46 [0.0s]\n", + " 9+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.1110, gap=13.89%, ItCnt=46 [0.0s]\n", + " 10+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.1110, gap=13.89%, ItCnt=50 [0.0s]\n", + " 11+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.0000, gap=12.50%, ItCnt=50 [0.0s]\n", + " 12+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.0000, gap=12.50%, ItCnt=51 [0.0s]\n", + " 13+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.0000, gap=12.50%, ItCnt=51 [0.0s]\n", + " 14+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.0000, gap=12.50%, ItCnt=53 [0.0s]\n" + ] + } + ], + "source": [ + "m5.clear_progress_listeners()\n", + "m5.add_progress_listener(TextProgressListener(clock='all'))\n", + "m5.solve(clean_before_solve=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, the listener is called more often, sometimes with identical objective and bound (see lines 3 and 4). This explains why the default clock is __Gap__ , to focus on actual changes.\n", + "\n", + "Other possible values for the clock enumerated type are:\n", + "\n", + " - __Solutions__: listen to all intermediate solutions, whether or not they improve objective or best bound.\n", + " - __Objective__: listen to intermediate solutions, which improve the objective.\n", + " \n", + "How exactly is improvement measured? A listener constructor can specify an _absdiff_ and _reldiff_ parameters which ar e interpreted as the minimal absolute (resp. relative) improvement to accept or not a call from CPLEX.\n", + "\n", + "Let us demonstrate this with a third `TextProgressListener` with clock set to 'objective' and an absolute diff of 1. We expect this listener to react whenever th eobjec5tive ha simprobed by an amount greater than 1:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1+: Node=0 Left=1 Best Integer=7.0000, Best Bound=10.0000, gap=42.86%, ItCnt=21 [0.0s]\n", + " 2+: Node=0 Left=1 Best Integer=8.0000, Best Bound=9.1960, gap=14.95%, ItCnt=41 [0.0s]\n" + ] + } + ], + "source": [ + "m5.clear_progress_listeners()\n", + "m5.add_progress_listener(TextProgressListener(clock='objective', absdiff=1, reldiff=0))\n", + "m5.solve(clean_before_solve=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As expected, the listener accepted three events, with the objective vaklues of 6,7 and 8." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Monitor progress: manage intermediate solutions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is done by another predefined listener class: `SolutionRecorder`. This type of listener is a subclass of the `SolutionListerer` class. Again, this listener contains a _clock_ parameter (default is __Gap__) which controls which events are accepted or not.\n", + "\n", + "The default behavior is to accept only solutions, which improve either the objective or the best bound" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from docplex.mp.progress import SolutionRecorder\n", + "\n", + "sol_recorder = SolutionRecorder()\n", + "m5.clear_progress_listeners()\n", + "m5.add_progress_listener(sol_recorder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, we solve with the `clean_before_solve` flag set to `True` to ensure a deterministic behavior." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "m5.solve(clean_before_solve=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At the end of the solve, the recorder contains all the intermediate solutions.\n", + "Now, let's display some information about those intermediate solutions." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* The recorder contains 5 solutions\n", + " - solution #1, obj=7.0, non-zero-values=7, total=7.0\n", + " - solution #2, obj=7.0, non-zero-values=7, total=7.0\n", + " - solution #3, obj=7.0, non-zero-values=7, total=7.0\n", + " - solution #4, obj=8.0, non-zero-values=8, total=8.0\n", + " - solution #5, obj=8.0, non-zero-values=8, total=8.0\n" + ] + } + ], + "source": [ + "# utility function to display recorded solutions in a recorder.\n", + "def display_recorded_solutions(rec):\n", + " print('* The recorder contains {} solutions'.format(rec.number_of_solutions))\n", + " for s, sol in enumerate(rec.iter_solutions(), start=1):\n", + " sumvals = sum(v for _, v in sol.iter_var_values())\n", + " print(' - solution #{0}, obj={1}, non-zero-values={2}, total={3}'.format(\n", + " s, sol.objective_value, sol.number_of_var_values, sumvals))\n", + " \n", + "display_recorded_solutions(sol_recorder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's try a solution recorder with a different clock: __Objective__. This recorder will record only intermediate solutions which improve the objective, regardless of the best bound. Such changes occur less frequently than the Gap clock, so we expect less solutions to be recorded." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* The recorder contains 2 solutions\n", + " - solution #1, obj=7.0, non-zero-values=7, total=7.0\n", + " - solution #2, obj=8.0, non-zero-values=8, total=8.0\n" + ] + } + ], + "source": [ + "sol_recorder2 = SolutionRecorder(clock='objective')\n", + "m5.clear_progress_listeners()\n", + "m5.add_progress_listener(sol_recorder2)\n", + "m5.solve(clean_before_solve=True)\n", + "display_recorded_solutions(sol_recorder2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As expected, the 'objective' recorder stored only 3 solutions instead of 5. Only one solution with objective 7 is recorded, instead of three with the 'Gap' recorder." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 3: Aborting the search with a custom progress listener\n", + "\n", + "MIP search can be time-consuming; insome cases, a 'good-enough' solution can be sufficient.\n", + "For example, when the gap is converging very slowly, it may be a good idea to stop and use the last solution instead of waiting for along time to prove optimality.\n", + "\n", + "Let's assume we want to implement the following behavior: \n", + "stop the search, when no improvement has occured in objective for N seconds since the latest improvements.\n", + "\n", + "The first question to ask is: what clock do we listen to? As we want to stop as soon as \n", + "elapsed time without improvement is greater than our limit, we listent to the higher frequency clock, `All` clock.\n", + "\n", + "Second, as we do not care for solutions, we sub-class from `ProgressListener`, not from `SolutionListener`.\n", + "\n", + "What do we need to code this aborter? we need to know whether an incumbent solution is present, and what is its objective value, then check whether the objective has improved or not.\n", + "If it has improved, we store the value of the objective and the time (obtained throught `ProgressData.time`),\n", + "if not, we check whether elapsed time is greater than the limit, and if it is the case, call method `abort()`.\n", + "\n", + "The code is as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "from docplex.mp.progress import ProgressListener\n", + "\n", + "class AutomaticAborter(ProgressListener):\n", + " \"\"\" a simple implementation of an automatic search stopper.\n", + " \"\"\"\n", + " def __init__(self, max_no_improve_time=10.):\n", + " super(AutomaticAborter, self).__init__(ProgressClock.All)\n", + " self.last_obj = None\n", + " self.last_obj_time = None\n", + " self.max_no_improve_time = max_no_improve_time\n", + " \n", + " def notify_start(self):\n", + " super(AutomaticAborter, self).notify_start()\n", + " self.last_obj = None\n", + " self.last_obj_time = None \n", + " \n", + " def is_improving(self, new_obj, eps=1e-4):\n", + " last_obj = self.last_obj\n", + " return last_obj is None or (abs(new_obj- last_obj) >= eps)\n", + " \n", + " def notify_progress(self, pdata):\n", + " super(AutomaticAborter, self).notify_progress(pdata)\n", + " if pdata.has_incumbent and self.is_improving(pdata.current_objective):\n", + " self.last_obj = pdata.current_objective\n", + " self.last_obj_time = pdata.time\n", + " print('----> #new objective={0}, time={1}s'.format(self.last_obj, self.last_obj_time))\n", + " else:\n", + " # a non improving move\n", + " last_obj_time = self.last_obj_time\n", + " this_time = pdata.time\n", + " if last_obj_time is not None:\n", + " elapsed = (this_time - last_obj_time)\n", + " if elapsed >= self.max_no_improve_time:\n", + " print('!! aborting cplex, elapsed={0} >= max_no_improve: {1}'.format(elapsed,\n", + " self.max_no_improve_time))\n", + " self.abort()\n", + " else:\n", + " print('----> non improving time={0}s'.format(elapsed))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We demonstrate the aborter on a bigger problem:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "----> #new objective=24.0, time=0.031000000002677552s\n", + "----> non improving time=0.0s\n", + "----> non improving time=0.01600000000325963s\n", + "----> non improving time=0.01600000000325963s\n", + "----> non improving time=0.04700000000593718s\n", + "----> non improving time=0.04700000000593718s\n", + "----> non improving time=0.07800000000861473s\n", + "----> non improving time=0.07800000000861473s\n", + "----> non improving time=0.09399999999732245s\n", + "----> non improving time=0.09399999999732245s\n", + "----> non improving time=0.125s\n", + "----> non improving time=0.125s\n", + "----> non improving time=0.15600000000267755s\n", + "----> non improving time=0.15600000000267755s\n", + "----> non improving time=0.1870000000053551s\n", + "----> non improving time=0.1870000000053551s\n", + "----> non improving time=0.21899999999732245s\n", + "----> non improving time=0.21899999999732245s\n", + "----> non improving time=0.2340000000112923s\n", + "----> non improving time=0.25s\n", + "----> non improving time=0.2649999999994179s\n", + "----> non improving time=0.2649999999994179s\n", + "----> non improving time=0.2970000000059372s\n", + "----> non improving time=0.2970000000059372s\n", + "----> non improving time=0.32800000000861473s\n", + "----> non improving time=0.32800000000861473s\n", + "----> non improving time=0.375s\n", + "----> non improving time=0.375s\n", + "----> non improving time=0.40600000000267755s\n", + "----> non improving time=0.40600000000267755s\n", + "----> non improving time=0.46800000000803266s\n", + "----> non improving time=0.4840000000112923s\n", + "----> non improving time=0.577000000004773s\n", + "----> non improving time=0.577000000004773s\n", + "----> non improving time=0.6090000000112923s\n", + "----> non improving time=0.6090000000112923s\n", + "----> non improving time=0.6090000000112923s\n", + "----> non improving time=0.6090000000112923s\n", + "----> non improving time=0.6090000000112923s\n", + "----> non improving time=0.6240000000107102s\n", + "----> non improving time=0.6240000000107102s\n", + "----> non improving time=0.6240000000107102s\n", + "----> non improving time=0.6399999999994179s\n", + "----> non improving time=0.6549999999988358s\n", + "----> non improving time=0.6549999999988358s\n", + "----> non improving time=0.6710000000020955s\n", + "----> non improving time=0.702000000004773s\n", + "----> non improving time=0.7180000000080327s\n", + "----> non improving time=0.7330000000074506s\n", + "----> non improving time=0.7649999999994179s\n", + "----> non improving time=0.7960000000020955s\n", + "----> non improving time=0.8430000000080327s\n", + "----> non improving time=0.8740000000107102s\n", + "----> non improving time=0.9210000000020955s\n", + "----> non improving time=0.952000000004773s\n", + "----> non improving time=0.9990000000107102s\n", + "----> non improving time=1.0299999999988358s\n", + "----> non improving time=1.077000000004773s\n", + "----> non improving time=1.1390000000101281s\n", + "----> non improving time=2.0130000000062864s\n", + "----> non improving time=2.059000000008382s\n", + "----> non improving time=2.1370000000024447s\n", + "----> non improving time=2.2150000000110595s\n", + "----> non improving time=2.309000000008382s\n", + "----> non improving time=2.3870000000024447s\n", + "----> non improving time=2.4180000000051223s\n", + "----> non improving time=2.4650000000110595s\n", + "----> non improving time=2.5120000000024447s\n", + "----> non improving time=2.559000000008382s\n", + "----> non improving time=2.6050000000104774s\n", + "----> non improving time=2.6680000000051223s\n", + "----> non improving time=2.6990000000078s\n", + "----> non improving time=2.745999999999185s\n", + "----> non improving time=2.7930000000051223s\n", + "----> non improving time=2.8550000000104774s\n", + "----> non improving time=2.9170000000012806s\n", + "----> non improving time=2.9800000000104774s\n", + "----> non improving time=3.0420000000012806s\n", + "----> non improving time=3.1200000000098953s\n", + "----> non improving time=3.18300000000454s\n", + "----> non improving time=3.2920000000012806s\n", + "----> non improving time=3.3540000000066357s\n", + "----> non improving time=3.4320000000006985s\n", + "----> non improving time=3.5100000000093132s\n", + "----> non improving time=3.573000000003958s\n", + "----> non improving time=3.665999999997439s\n", + "----> non improving time=3.7600000000093132s\n", + "----> non improving time=3.853000000002794s\n", + "----> non improving time=3.9470000000001164s\n", + "!! aborting cplex, elapsed=4.040999999997439 >= max_no_improve: 4\n", + "!! aborting cplex, elapsed=4.103000000002794 >= max_no_improve: 4\n" + ] + } + ], + "source": [ + "large_hearts = build_hearts(12)\n", + "#large_hearts.add_progress_listener(TextProgressListener(clock='gap'))\n", + "# maximum non-improving time is 4 seconds.\n", + "large_hearts.add_progress_listener(AutomaticAborter(max_no_improve_time=4))\n", + "# again use clean_before_solve to ensure deterministic run of this cell.\n", + "large_hearts.solve(clean_before_solve=True, log_output=False);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Though the solve has been aborted, it returned the latest solution,\n", + "but the status of the solve shows it hhas been aborted." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* solution has objective 24\n", + "* solve status is 'aborted'\n" + ] + } + ], + "source": [ + "large_s = large_hearts.solution\n", + "print('* solution has objective {0}'.format(large_s.objective_value))\n", + "print(\"* solve status is '{}'\".format(large_hearts.solve_details.status))" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "### Step 5: Produce advancement charts\n", + "\n", + "Progress listeners may also be used to generate visual charts to plot the advancement of the solve.\n", + "For example, let's assume we want to plot the chart of the gap vs. time.\n", + "In a first version, we will just print the value of the gap and time.\n", + "What clock do we listen to? obviously we listen to the `Gap` clock, and do not care for solution values,\n", + "so we need a sub-class of `ProgressListener`.\n", + "\n", + "As this listener has no internal data, there is no need to write a `notify_start` method.\n", + "\n", + "The `notify_progress` consist in printing a formatted message with the gap and time.\n", + "\n", + "The code is as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "class MipGapPrinter(ProgressListener):\n", + " \n", + " def __init__(self):\n", + " ProgressListener.__init__(self, ProgressClock.Gap)\n", + " \n", + " def notify_progress(self, pdata):\n", + " gap = pdata.mip_gap\n", + " ms_time = 1000* pdata.time\n", + " print('-- new gap: {0:.1%}, time: {1:.0f} ms'.format(gap, ms_time))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-- new gap: 71.4%, time: 0 ms\n", + "-- new gap: 65.2%, time: 16 ms\n", + "-- new gap: 63.1%, time: 16 ms\n", + "-- new gap: 60.9%, time: 31 ms\n", + "-- new gap: 59.9%, time: 31 ms\n", + "-- new gap: 58.9%, time: 47 ms\n", + "-- new gap: 58.1%, time: 47 ms\n", + "-- new gap: 56.2%, time: 78 ms\n", + "-- new gap: 54.9%, time: 78 ms\n", + "-- new gap: 54.1%, time: 94 ms\n", + "-- new gap: 53.3%, time: 125 ms\n" + ] + } + ], + "source": [ + "m8 = build_hearts(8)\n", + "m8.add_progress_listener(MipGapPrinter())\n", + "m8.solve(clean_before_solve=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Variant: using matplotlib to plot a chart of gap vs. time\n", + "\n", + "In this variant, we use `matplotlib` to chart the evolution of gap over time. The logic of the custom listener is exactly similar to the gap printer, except that we call matplotlib.plot instead of printing a message." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "from docplex.mp.progress import ProgressListener, ProgressClock\n", + "from IPython import display\n", + "\n", + "class MipGapPlotter(ProgressListener):\n", + " \n", + " def __init__(self):\n", + " ProgressListener.__init__(self, ProgressClock.Gap)\n", + " plt.ion()\n", + " self.fig = plt.figure(figsize=(10,4))\n", + " self.ax = self.fig.add_subplot(1,1,1)\n", + " \n", + " def notify_start(self):\n", + " self.times =[]\n", + " self.gaps = []\n", + " #self.lines, = ax.plot([],[], 'o')\n", + " plt.xlabel('time (ms)')\n", + " plt.ylabel('gap (%)')\n", + " \n", + " def notify_progress(self, pdata):\n", + " gap = pdata.mip_gap\n", + " time = pdata.time\n", + " self.times.append(1000* time)\n", + " self.gaps.append(100*gap)\n", + " plt.plot(self.times, self.gaps, 'go-')\n", + " display.display(plt.gcf())\n", + " display.clear_output(wait=True)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "m9 = build_hearts(9)\n", + "m9.add_progress_listener(MipGapPlotter())\n", + "m9.solve(clean_before_solve=True);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "\n", + "You learned how to set up and use the IBM Decision Optimization CPLEX Modeling for Python to formulate a Mathematical Programming model and track its progress." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "render": true + }, + "source": [ + "#### References\n", + "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", + "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\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 © 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.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/examples/mp/jupyter/sparktrans/SparkML_transformers.ipynb b/examples/mp/jupyter/sparktrans/SparkML_transformers.ipynb index 6effa92..84cbd2e 100644 --- a/examples/mp/jupyter/sparktrans/SparkML_transformers.ipynb +++ b/examples/mp/jupyter/sparktrans/SparkML_transformers.ipynb @@ -3,9 +3,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import display, HTML\n", @@ -133,9 +131,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "nb_foods = len(FOODS)\n", @@ -156,9 +152,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -197,9 +191,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mat_fn = np.matrix([FOOD_NUTRIENTS[f][1:] for f in range(nb_foods)])\n", @@ -252,9 +244,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mat_nf.shape" @@ -298,9 +288,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "food_nutrients_df.printSchema()" @@ -309,9 +297,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "food_nutrients_df.show()" @@ -337,9 +323,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.sparktrans.transformers import CplexRangeTransformer\n", @@ -368,9 +352,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "food_nutrients_LP_df = food_nutrients_df.select([item for item in food_nutrients_df.columns if item not in ['min']])\n", @@ -380,9 +362,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.sparktrans.transformers import CplexTransformer\n", @@ -400,9 +380,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -428,9 +406,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "diet_max_cost = diet_max_cost_df.toPandas()\n", @@ -449,21 +425,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, diff --git a/examples/mp/jupyter/sports_scheduling.ipynb b/examples/mp/jupyter/sports_scheduling.ipynb index 7a36eef..6155914 100644 --- a/examples/mp/jupyter/sports_scheduling.ipynb +++ b/examples/mp/jupyter/sports_scheduling.ipynb @@ -13,13 +13,10 @@ "\n", "When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n", "\n", - ">This notebook is part of the [Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", - "\n", - ">Running the sample requires the installation of\n", - " [CPLEX Optimization studio](https://www.ibm.com/products/ilog-cplex-optimization-studio)\n", - " (Commercial or free \n", - " [CPLEX Community edition](https://www.ibm.com/account/reg/us-en/signup?formid=urx-20028>`)).\n", - " This sample automatically installs *CPLEX CE* if needed.\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", @@ -108,10 +105,8 @@ }, { "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -130,10 +125,8 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -159,9 +152,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Teams in 1st division\n", @@ -180,9 +171,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "#number_of_matches_to_play = 1 # Number of match to play between two teams on the league\n", @@ -203,9 +192,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "CSS = \"\"\"\n", @@ -267,9 +254,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", @@ -292,9 +277,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.display import display\n", @@ -319,9 +302,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", @@ -346,9 +327,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from collections import namedtuple\n", @@ -369,9 +348,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "nb_play = { m : number_of_matches_inside_division if m.is_divisional==1 \n", @@ -391,9 +368,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -412,9 +387,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -432,9 +405,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plays = mdl.binary_var_matrix(matches, weeks, lambda ij: \"x_%s_%d\" %(str(ij[0]), ij[1]))" @@ -459,9 +430,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints( mdl.sum(plays[m,w] for w in weeks) == nb_play[m]\n", @@ -479,9 +448,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints( mdl.sum(plays[m,w] for m in matches if (m.team1 == t or m.team2 == t) ) == 1\n", @@ -499,9 +466,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints( plays[m,w] + plays[m,w+1] <= 1 \n", @@ -521,9 +486,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.add_constraints( mdl.sum(plays[m,w] for w in first_half_weeks for m in matches \n", @@ -546,9 +509,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "gain = { w : w*w for w in weeks}\n", @@ -571,9 +532,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "mdl.print_information()\n", @@ -602,9 +561,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "try: # Python 2\n", @@ -619,9 +576,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "sol = namedtuple(\"solution\",[\"week\",\"is_divisional\", \"team1\", \"team2\"])\n", @@ -632,9 +587,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "nfl_finals = [(\"2016\", \"Carolina Panthers\", \"Denver Broncos\"),\n", @@ -657,7 +610,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "render": true }, "outputs": [], @@ -675,9 +627,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "months = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \n", @@ -698,7 +648,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false, "render": true }, "outputs": [], @@ -728,7 +677,7 @@ "#### References\n", "* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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" ] }, @@ -736,30 +685,37 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "gist_id": "6011986", "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/jupyter/tutorials/Beyond_Linear_Programming.ipynb b/examples/mp/jupyter/tutorials/Beyond_Linear_Programming.ipynb index dc8a7b1..02fdf2e 100644 --- a/examples/mp/jupyter/tutorials/Beyond_Linear_Programming.ipynb +++ b/examples/mp/jupyter/tutorials/Beyond_Linear_Programming.ipynb @@ -12,12 +12,11 @@ "\n", "After completing this unit, you should be able to describe what a network model is, and the benefits of using network models, explain the concepts of nonlinearity and convexity, describe what a piecewise linear function is, and describe the differences between Linear Programming (LP), Integer Programming (IP), Mixed-Integer Programming (MIP), and Quadratic Programming (QP). You should also be able to construct a simple MIP model. \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", - ">This notebook is part of [Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/).\n", - "\n", - ">It requires a valid subscription to **Decision Optimization on Cloud** or a **local installation of CPLEX Optimizers**. \n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud).\n", "\n", "Table of contents:\n", "\n", @@ -221,9 +220,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -243,9 +240,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# create flow variables for each couple of nodes\n", @@ -297,9 +292,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "tm.minimize(tm.sum(x[i,j]*costs.get((i,j), 0)))" @@ -323,9 +316,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "tms = tm.solve(url=url, key=key)\n", @@ -524,9 +515,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Display matplotlib plots in the notebook\n", @@ -556,9 +545,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "pwf2 = pm.piecewise(preslope=0, breaksxy=[(0, 0), (1000, 400), (3000, 800)], postslope=0.1)\n", @@ -576,9 +563,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "x = pm.continuous_var(name='x')\n", @@ -635,9 +620,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "im = Model(name='integer_programming')\n", @@ -733,9 +716,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "lm = Model(name='lp_telephone_production')\n", @@ -772,9 +753,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "im = Model(name='ip_telephone_production')\n", @@ -907,9 +886,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "bbm = Model(name='b&b')\n", @@ -977,9 +954,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# variables for total production\n", @@ -1011,9 +986,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "\n", @@ -1063,9 +1036,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "tm2s= tm2.solve(url=url, key=key,log_output=True)\n", @@ -1187,9 +1158,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "sec_data = {\n", @@ -1219,9 +1188,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# the variance matrix\n", @@ -1252,9 +1219,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "def is_nam(s):\n", @@ -1388,9 +1353,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "assert mdl.solve(url=url, key=key), \"Solve failed\"\n", @@ -1407,9 +1370,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "all_fracs = {}\n", @@ -1429,9 +1390,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", @@ -1464,9 +1423,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "target_returns = range(5,21) # from 5 to 20, included\n", @@ -1491,9 +1448,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "plt.plot(target_returns, variances, 'bo-')\n", @@ -1541,7 +1496,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] } @@ -1549,21 +1504,21 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, diff --git a/examples/mp/jupyter/tutorials/Linear_Programming.ipynb b/examples/mp/jupyter/tutorials/Linear_Programming.ipynb index 5efbcb6..a362e5b 100644 --- a/examples/mp/jupyter/tutorials/Linear_Programming.ipynb +++ b/examples/mp/jupyter/tutorials/Linear_Programming.ipynb @@ -15,11 +15,11 @@ "You should also be able to describe some of the algorithms used to solve LPs, explain what presolve does, and recognize the elements of an LP in a basic DOcplex model.\n", "\n", "\n", - ">This notebook is part of [Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/).\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", - ">It requires a valid subscription to **Decision Optimization on Cloud** or a **local installation of CPLEX Optimizers**. \n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud).\n", "\n", "Table of contents:\n", "\n", @@ -249,9 +249,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -324,9 +322,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# by default, all variables in Docplex have a lower bound of 0 and infinite upper bound\n", @@ -347,9 +343,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# write constraints\n", @@ -402,9 +396,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.print_information()" @@ -484,9 +476,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "s = m.solve(url=url, key=key)\n", @@ -597,9 +587,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# create a new model, copy of m\n", @@ -694,9 +682,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "overtime = m.continuous_var(name='overtime', ub=40)" @@ -733,9 +719,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.maximize(12*desk + 20 * cell - 2 * overtime)" @@ -751,9 +735,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "s2 = m.solve(url=url, key=key)\n", @@ -952,9 +934,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "print('* desk variable has reduced cost: {0}'.format(desk.reduced_cost))\n", @@ -1020,9 +1000,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# revert soft constraints\n", @@ -1065,9 +1043,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "m.parameters.lpmethod = 4\n", @@ -1161,7 +1137,7 @@ "## References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] } @@ -1169,21 +1145,21 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.11" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, diff --git a/examples/mp/jupyter/ucp_pandas.ipynb b/examples/mp/jupyter/ucp_pandas.ipynb index c298f29..b44a471 100644 --- a/examples/mp/jupyter/ucp_pandas.ipynb +++ b/examples/mp/jupyter/ucp_pandas.ipynb @@ -10,11 +10,10 @@ "\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 an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html)\n", - "\n", - "Discover us [here](https://developer.ibm.com/docloud)\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", @@ -80,9 +79,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import pip\n", @@ -113,9 +110,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import sys\n", @@ -141,9 +136,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", @@ -166,9 +159,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML\n", @@ -187,9 +178,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "energies = [\"coal\", \"gas\", \"diesel\", \"wind\"]\n", @@ -209,9 +198,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "all_units = [\"coal1\", \"coal2\", \n", @@ -258,9 +245,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Add a derived co2-cost column by merging with df_energies\n", @@ -282,9 +267,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "raw_demand = [1259.0, 1439.0, 1289.0, 1211.0, 1433.0, 1287.0, 1285.0, 1227.0, 1269.0, 1158.0, 1277.0, 1417.0, 1294.0, 1396.0, 1414.0, 1386.0,\n", @@ -318,9 +301,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.environment import Environment\n", @@ -339,9 +320,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -366,9 +345,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "units = all_units\n", @@ -395,9 +372,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Organize all decision variables in a DataFrame indexed by 'units' and 'periods'\n", @@ -423,9 +398,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a join between 'df_decision_vars' and 'df_up' Data Frames based on common index id (ie: 'units')\n", @@ -439,9 +412,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import pandas as pb\n", @@ -451,9 +422,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# When in use, the production level is constrained to be between min and max generation.\n", @@ -473,9 +442,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Initial state\n", @@ -509,9 +476,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Use groupby operation to process each unit\n", @@ -543,9 +508,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Turn_on, turn_off\n", @@ -576,9 +539,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Minimum uptime, downtime\n", @@ -608,9 +569,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Enforcing demand\n", @@ -638,9 +597,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a join between 'df_decision_vars' and 'df_up' Data Frames based on common index ids (ie: 'units')\n", @@ -655,9 +612,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# objective\n", @@ -695,9 +650,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ucpm.print_information()" @@ -706,9 +659,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "assert ucpm.solve(), \"!!! Solve of the model fails\"" @@ -717,9 +668,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ucpm.report()" @@ -739,9 +688,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_prods = df_decision_vars.production.apply(lambda v: v.solution_value).unstack(level='units')\n", @@ -764,9 +711,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_spins = DataFrame(df_up.max_gen.to_dict(), index=periods) - df_prods\n", @@ -785,9 +730,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_spins.coal2.plot(style='o-', ylim=[0,200])" @@ -805,9 +748,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "global_spin = df_spins.sum(axis=1)\n", @@ -827,9 +768,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "df_used.sum(axis=1).plot(title=\"Number of plants online\", kind='line', style=\"r-\", ylim=[0, len(units)])" @@ -845,9 +784,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# extract unit cost data\n", @@ -876,9 +813,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Calculate sum by column (by default, axis = 0) to get total cost for each unit\n", @@ -910,9 +845,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# first retrieve the co2 and economic kpis\n", @@ -930,9 +863,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "assert ucpm.solve(), \"Solve failed\"" @@ -941,9 +872,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "min_co2_cost = ucpm.objective_value\n", @@ -965,9 +894,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# minimize only economic cost\n", @@ -977,9 +904,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "assert ucpm.solve(), \"Solve failed\"" @@ -988,9 +913,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "min_eco_cost = ucpm.objective_value\n", @@ -1019,9 +942,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# add extra variable\n", @@ -1034,9 +955,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "co2min = min_co2_cost\n", @@ -1065,9 +984,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "#explore the co2/eco frontier in 50 points\n", @@ -1077,9 +994,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# normalize all values by dividing by their maximum\n", @@ -1121,7 +1036,7 @@ "#### References\n", "* [CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n", "* [Decision Optimization on Cloud](https://developer.ibm.com/docloud/)\n", - "* Need help with DOcplex or to report a bug? Please go [here](https://developer.ibm.com/answers/smartspace/docloud).\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." ] }, @@ -1129,29 +1044,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017-2018 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.6.1" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/mp/modeling/diet.py b/examples/mp/modeling/diet.py index 4f14801..8d2cc82 100644 --- a/examples/mp/modeling/diet.py +++ b/examples/mp/modeling/diet.py @@ -60,7 +60,7 @@ # Build the model # ---------------------------------------------------------------------------- -def build_diet_model(**kwargs): +def build_diet_model(name='diet', **kwargs): # Create tuples with named fields for foods and nutrients foods = [Food(*f) for f in FOODS] @@ -70,7 +70,7 @@ def build_diet_model(**kwargs): fn[1 + n] for fn in FOOD_NUTRIENTS for n in range(len(NUTRIENTS))} # Model - mdl = Model(name='diet', **kwargs) + mdl = Model(name=name, **kwargs) # Decision variables, limited to be >= Food.qmin and <= Food.qmax qty = mdl.continuous_var_dict(foods, lb=lambda f: f.qmin, ub=lambda f: f.qmax, name=lambda f: "q_%s" % f.name) diff --git a/examples/mp/modeling/nurses.py b/examples/mp/modeling/nurses.py index 3cf2301..6d4d953 100644 --- a/examples/mp/modeling/nurses.py +++ b/examples/mp/modeling/nurses.py @@ -284,7 +284,7 @@ def solve(model, **kwargs): def load_data(model, shifts_, nurses_, nurse_skills, vacations_=None, - nurse_associations_=None, nurse_imcompatibilities_=None): + nurse_associations_=None, nurse_imcompatibilities_=None, verbose=True): """ Usage: load_data(shifts, nurses, nurse_skills, vacations) """ model.number_of_overlaps = 0 model.work_rules = DEFAULT_WORK_RULES @@ -302,12 +302,12 @@ def load_data(model, shifts_, nurses_, nurse_skills, vacations_=None, # computed model.departments = set(sh.department for sh in model.shifts) - - print('#nurses: {0}'.format(len(model.nurses))) - print('#shifts: {0}'.format(len(model.shifts))) - print('#vacations: {0}'.format(len(model.vacations))) - print("#associations=%d" % len(model.nurse_associations)) - print("#incompatibilities=%d" % len(model.nurse_incompatibilities)) + if verbose: + print('#nurses: {0}'.format(len(model.nurses))) + print('#shifts: {0}'.format(len(model.shifts))) + print('#vacations: {0}'.format(len(model.vacations))) + print("#associations=%d" % len(model.nurse_associations)) + print("#incompatibilities=%d" % len(model.nurse_incompatibilities)) def setup_data(model): @@ -439,9 +439,14 @@ def setup_constraints(model): model.add_constraint(nurse_assigned[nurse1, s] + nurse_assigned[nurse2, s] <= 1, ctname) model.total_number_of_assignments = model.sum(nurse_assigned[n, s] for n in all_nurses for s in all_shifts) - model.nurse_costs = [model.nurse_assignment_vars[n, s] * n.pay_rate * model.shift_activities[s].duration for n in - model.nurses - for s in model.shifts] + # model.nurse_costs = [model.nurse_assignment_vars[n, s] * n.pay_rate * model.shift_activities[s].duration + # for n in model.nurses for s in model.shifts] + + def assignment_cost_f(ns): + n, s = ns + return n.pay_rate * model.shift_activities[s].duration + + model.nurse_costs = model.scal_prod_f(nurse_assigned, assignment_cost_f) model.total_salary_cost = model.sum(model.nurse_costs) @@ -498,10 +503,10 @@ def print_solution(model): # Build the model # ---------------------------------------------------------------------------- -def build(context=None, **kwargs): +def build(context=None, verbose=False, **kwargs): mdl = Model("Nurses", context=context, **kwargs) load_data(mdl, SHIFTS, NURSES, NURSE_SKILLS, VACATIONS, NURSE_ASSOCIATIONS, - NURSE_INCOMPATIBILITIES) + NURSE_INCOMPATIBILITIES, verbose=verbose) setup_data(mdl) setup_variables(mdl) setup_constraints(mdl) diff --git a/examples/mp/modeling/nurses_multiobj.py b/examples/mp/modeling/nurses_multiobj.py index e28df15..bc9d96f 100644 --- a/examples/mp/modeling/nurses_multiobj.py +++ b/examples/mp/modeling/nurses_multiobj.py @@ -459,8 +459,7 @@ def setup_objective(model): total_fairness = total_over_average_worktime + total_under_average_worktime model.add_kpi(total_fairness, "Total fairness") - model.set_multi_objective(ObjectiveSense.Minimize, [model.total_salary_cost, total_fairness, model.total_number_of_assignments], - priorities=[2, 3, 1]) + model.minimize_static_lex([model.total_salary_cost, total_fairness, model.total_number_of_assignments]) def print_information(model): diff --git a/examples/mp/modeling/production.py b/examples/mp/modeling/production.py index bd1b650..6a8b3ea 100644 --- a/examples/mp/modeling/production.py +++ b/examples/mp/modeling/production.py @@ -87,6 +87,9 @@ def print_production_solution(mdl, products): (product=p[0], out_var=mdl.outside_vars[p].solution_value)) +def build_default_production_problem(**kwargs): + return build_production_problem(PRODUCTS, RESOURCES, CONSUMPTIONS, **kwargs) + # ---------------------------------------------------------------------------- # Solve the model and display the result # ---------------------------------------------------------------------------- diff --git a/examples/mp/workflow/load_balancing.py b/examples/mp/workflow/load_balancing.py index 241ad3c..0c7f03b 100644 --- a/examples/mp/workflow/load_balancing.py +++ b/examples/mp/workflow/load_balancing.py @@ -7,10 +7,8 @@ # Source: http://blog.yhathq.com/posts/how-yhat-does-cloud-balancing.html from collections import namedtuple -import json -from docplex.util.environment import get_environment -from docplex.mp.absmodel import AbstractModel +from docplex.mp.model import Model # ---------------------------------------------------------------------------- @@ -20,6 +18,7 @@ class TUser(namedtuple("TUser", ["id", "running", "sleeping", "current_server"]) def __str__(self): return self.id + SERVERS = ["server002", "server003", "server001", "server006", "server007", "server004", "server005"] USERS = [("user013", 2, 1, "server002"), @@ -112,166 +111,136 @@ def __str__(self): DEFAULT_MAX_PROCESSES_PER_SERVER = 50 +def _is_migration(user, server): + """ Returns True if server is not the user's current + Used in setup of constraints. + """ + return server != user.current_server + + # ---------------------------------------------------------------------------- # Build the model # ---------------------------------------------------------------------------- -class LoadBalancingModel(AbstractModel): - def __init__(self, **kwargs): - AbstractModel.__init__(self, 'load_balancing', **kwargs) - # raw data - self.max_processes_per_server = DEFAULT_MAX_PROCESSES_PER_SERVER - self.servers = [] - self.users = [] - # decision objects - self.active_var_by_server = {} - self.assign_user_to_server_vars = {} - self.number_of_active_servers = None - self.number_of_migrations = None - self.max_sleeping_workload = None - - def load_data(self, servers, users, max_process_per_server=DEFAULT_MAX_PROCESSES_PER_SERVER): - self.servers = servers - self.users = [TUser(*user_row) for user_row in users] - self.max_processes_per_server = max_process_per_server - - def clear(self): - AbstractModel.clear(self) - self.active_var_by_server = {} - self.assign_user_to_server_vars = {} - self.number_of_active_servers = None - - def setup_variables(self): - all_servers = self.servers - all_users = self.users - - self.active_var_by_server = self.binary_var_dict(all_servers, name='isActive') - - def user_server_pair_namer(u_s): - u, s = u_s - return '%s_to_%s' % (u.id, s) - - self.assign_user_to_server_vars = self.binary_var_matrix(all_users, all_servers, user_server_pair_namer) - - @staticmethod - def _is_migration(user, server): - """ Returns True if server is not the user's current - Used in setup of constraints. - """ - return server != user.current_server - - def setup_constraints(self): - mdl = self - all_servers = self.servers - all_users = self.users - - max_proc_per_server = self.max_processes_per_server - - mdl.add_constraints( - mdl.sum(self.assign_user_to_server_vars[u, s] * u.running for u in all_users) <= max_proc_per_server - for s in all_servers) - - # each assignment var is <= active_server(s) - for s in all_servers: - for u in all_users: - ct_name = 'ct_assign_to_active_{0!s}_{1!s}'.format(u, s) - mdl.add_constraint(self.assign_user_to_server_vars[u, s] <= self.active_var_by_server[s], ct_name) + +def build_load_balancing_model(servers, users_, max_process_per_server=DEFAULT_MAX_PROCESSES_PER_SERVER, **kwargs): + m = Model(name='load_balancing', **kwargs) + + # decision objects + + users = [TUser(*user_row) for user_row in users_] + + active_var_by_server = m.binary_var_dict(servers, name='isActive') + + def user_server_pair_namer(u_s): + u, s = u_s + return '%s_to_%s' % (u.id, s) + + assign_user_to_server_vars = m.binary_var_matrix(users, servers, user_server_pair_namer) + + m.add_constraints( + m.sum(assign_user_to_server_vars[u, s] * u.running for u in users) <= max_process_per_server for s in servers) + # each assignment var is <= active_server(s) + for s in servers: + for u in users: + ct_name = 'ct_assign_to_active_{0!s}_{1!s}'.format(u, s) + m.add_constraint(assign_user_to_server_vars[u, s] <= active_var_by_server[s], ct_name) # sum of assignment vars for (u, all s in servers) == 1 - for u in all_users: + for u in users: ct_name = 'ct_unique_server_%s' % (u[0]) - mdl.add_constraint(mdl.sum((self.assign_user_to_server_vars[u, s] for s in all_servers)) == 1.0, ct_name) - - def setup_objective(self): - mdl = self - self.number_of_active_servers = mdl.sum((self.active_var_by_server[svr] for svr in self.servers)) - self.add_kpi(self.number_of_active_servers, "Number of active servers") - - self.number_of_migrations = mdl.sum( - self.assign_user_to_server_vars[u, s] for u in self.users for s in self.servers if - self._is_migration(u, s)) - mdl.add_kpi(self.number_of_migrations, "Total number of migrations") - - max_sleeping_workload = mdl.integer_var(name="max_sleeping_processes") - for s in self.servers: - ct_name = 'ct_define_max_sleeping_%s' % s - mdl.add_constraint( - mdl.sum( - self.assign_user_to_server_vars[u, s] * u.sleeping for u in self.users) <= max_sleeping_workload, - ct_name) - mdl.add_kpi(max_sleeping_workload, "Max sleeping workload") - self.max_sleeping_workload = max_sleeping_workload - # Set objective function - mdl.minimize(self.number_of_active_servers) - - def run(self, **kwargs): - mdl = self - mdl.ensure_setup() - mdl.print_information() - # build an ordered sequence of goals - ordered_kpi_keywords = ["servers", "migrations", "sleeping"] - ordered_goals = [mdl.kpi_by_name(k) for k in ordered_kpi_keywords] - - return mdl.solve_lexicographic(ordered_goals, **kwargs) - - def report(self): - mdl = self - active_servers = sorted([s for s in mdl.servers if mdl.active_var_by_server[s].solution_value == 1]) - print("Active Servers: {}".format(active_servers)) - print("*** User assignment ***") - for (u, s) in sorted(mdl.assign_user_to_server_vars): - if mdl.assign_user_to_server_vars[(u, s)].solution_value == 1: - print("{} uses {}, migration: {}".format(u, s, "yes" if mdl._is_migration(u, s) else "no")) - print("*** Servers sleeping processes ***") - for s in active_servers: - sleeping = sum(self.assign_user_to_server_vars[u, s].solution_value * u.sleeping for u in self.users) - print("Server: {} #sleeping={}".format(s, sleeping)) - - def save_solution_as_json(self, json_file): - """Saves the solution for this model as JSON. - - Note that this is not a CPLEX Solution file, as this is the result of post-processing a CPLEX solution - """ - mdl = self - solution_dict = {} - # active server - active_servers = sorted([s for s in mdl.servers if mdl.active_var_by_server[s].solution_value == 1]) - solution_dict["active servers"] = active_servers - - # sleeping processes by server - sleeping_processes = {} - for s in active_servers: - sleeping = sum(self.assign_user_to_server_vars[u, s].solution_value * u.sleeping for u in self.users) - sleeping_processes[s] = sleeping - solution_dict["sleeping processes by server"] = sleeping_processes - - # user assignment - user_assignment = [] - for (u, s) in sorted(mdl.assign_user_to_server_vars): - if mdl.assign_user_to_server_vars[(u, s)].solution_value == 1: - n = { - 'user': u.id, - 'server': s, - 'migration': "yes" if mdl._is_migration(u, s) else "no" - } - user_assignment.append(n) - solution_dict['user assignment'] = user_assignment - json_file.write(json.dumps(solution_dict, indent=3).encode('utf-8')) - - -class DefaultLoadBalancingModel(LoadBalancingModel): - def __init__(self, context=None, **kwargs): - LoadBalancingModel.__init__(self, context=context, **kwargs) - self.load_data(SERVERS, USERS) - + m.add_constraint(m.sum((assign_user_to_server_vars[u, s] for s in servers)) == 1, ct_name) + + number_of_active_servers = m.sum((active_var_by_server[svr] for svr in servers)) + m.add_kpi(number_of_active_servers, "Number of active servers") + + number_of_migrations = m.sum( + assign_user_to_server_vars[u, s] for u in users for s in servers if + _is_migration(u, s)) + m.add_kpi(number_of_migrations, "Total number of migrations") + + max_sleeping_workload = m.integer_var(name="max_sleeping_processes") + for s in servers: + ct_name = 'ct_define_max_sleeping_%s' % s + m.add_constraint( + m.sum( + assign_user_to_server_vars[u, s] * u.sleeping for u in users) <= max_sleeping_workload, + ct_name) + m.add_kpi(max_sleeping_workload, "Max sleeping workload") + # Set objective function + # m.minimize(number_of_active_servers) + m.minimize_static_lex([number_of_active_servers, number_of_migrations, max_sleeping_workload]) + + # attach artefacts to model for reporting + m.users = users + m.servers = servers + m.active_var_by_server = active_var_by_server + m.assign_user_to_server_vars = assign_user_to_server_vars + m.max_sleeping_workload = max_sleeping_workload + + return m + + +def lb_report(mdl): + active_servers = sorted([s for s in mdl.servers if mdl.active_var_by_server[s].solution_value == 1]) + print("Active Servers: {0} = {1}".format(len(active_servers), active_servers)) + print("*** User/server assignments , #migrations={0} ***".format( + mdl.kpi_by_name("number of migrations").solution_value)) + # for (u, s) in sorted(mdl.assign_user_to_server_vars): + # if mdl.assign_user_to_server_vars[(u, s)].solution_value == 1: + # print("{} uses {}, migration: {}".format(u, s, "yes" if _is_migration(u, s) else "no")) + print("*** Servers sleeping processes ***") + for s in active_servers: + sleeping = sum(mdl.assign_user_to_server_vars[u, s].solution_value * u.sleeping for u in mdl.users) + print("Server: {} #sleeping={}".format(s, sleeping)) + + +def make_default_load_balancing_model(**kwargs): + return build_load_balancing_model(SERVERS, USERS, **kwargs) + + +def lb_save_solution_as_json(mdl, json_file): + """Saves the solution for this model as JSON. + + Note that this is not a CPLEX Solution file, as this is the result of post-processing a CPLEX solution + """ + import json + solution_dict = {} + # active server + active_servers = sorted([s for s in mdl.servers if mdl.active_var_by_server[s].solution_value == 1]) + solution_dict["active servers"] = active_servers + + # sleeping processes by server + sleeping_processes = {} + for s in active_servers: + sleeping = sum(mdl.assign_user_to_server_vars[u, s].solution_value * u.sleeping for u in mdl.users) + sleeping_processes[s] = sleeping + solution_dict["sleeping processes by server"] = sleeping_processes + +# user assignment + user_assignment = [] + for (u, s) in sorted(mdl.assign_user_to_server_vars): + if mdl.assign_user_to_server_vars[(u, s)].solution_value == 1: + n = { + 'user': u.id, + 'server': s, + 'migration': "yes" if _is_migration(u, s) else "no" + } + user_assignment.append(n) + solution_dict['user assignment'] = user_assignment + json_file.write(json.dumps(solution_dict, indent=3).encode('utf-8')) # ---------------------------------------------------------------------------- # Solve the model and display the result # ---------------------------------------------------------------------------- + if __name__ == '__main__': - lbm = DefaultLoadBalancingModel() + lbm = make_default_load_balancing_model() # Run the model. - if lbm.run(): - lbm.report() - with get_environment().get_output_stream("solution.json") as fp: - lbm.save_solution_as_json(fp) + lbs = lbm.solve(log_output=True) + lb_report(lbm) + # save json, used in worker tests + from docplex.util.environment import get_environment + with get_environment().get_output_stream("solution.json") as fp: + lb_save_solution_as_json(lbm, fp) +