{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial was written by Michael Schmitt, a student of the course \"Discrete Optimization\".\n",
    "\n",
    "# Branch & Bound\n",
    "\n",
    "In this Tutorial, I like to describe, how one can implement the Branch & Bound Algorithm for polytopes using polymake.\n",
    "\n",
    "## Preamble\n",
    "\n",
    "This preamble must be integrated in every script file, so that we can use the polytope functionalities of polymake."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "use application \"polytope\";"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At first we create some global variables that keep track over the best integer solution, that has been discovered so far.   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "declare $z = -10000;\n",
    "declare $x_best;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Subfunction\n",
    "\n",
    "I introduce a subroutine, that returns the non-integer part of some rational number."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "    sub f {\n",
    "        my $x=shift;\n",
    "        my $denom = denominator($x);\n",
    "        my $nom = convert_to<Integer>($denom * $x);\n",
    "        my $remainder = $nom % $denom;\n",
    "        if($remainder < 0){\n",
    "          $remainder += $denom;\n",
    "        }\n",
    "        return new Rational($remainder, $denom);\n",
    "    }"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Main routine\n",
    "\n",
    "It follows the main routine, that will call itself recursively. The parameters that are passed to this function are:\n",
    "\n",
    "*  The objective function as an Vector<Rational>\n",
    "\n",
    "*  A list of inequalities that define the currently examined polytope"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "sub branchnbound {\n",
    "    \n",
    "\n",
    "# The following line gets the parameters.\n",
    "\n",
    "    my $objective = shift;\n",
    "    my @ineqs = @_;\n",
    "    \n",
    "    my $mat = new Matrix<Rational>(@ineqs);\n",
    "    #print \"Current Inequalities: \\n\", $mat, \"\\n\";\n",
    "\n",
    "    my $p = new Polytope<Rational>;\n",
    "    $p->INEQUALITIES = $mat;\n",
    "\n",
    "\n",
    "### Feasibility check\n",
    "# Let's check, whether the polytope is empty or not.\n",
    "# If it is empty, we can stop examining this branch of the B&B Tree.\n",
    "    \n",
    "    if(!$p->FEASIBLE){\n",
    "        print \"EMPTY!\\n\";\n",
    "        return;\n",
    "    }\n",
    "\n",
    "\n",
    "### Solving the relaxation\n",
    "# We look for the optimal solution of the linear relaxation.\n",
    " \n",
    "    $p->LP = new LinearProgram<Rational>(LINEAR_OBJECTIVE=>$objective);\n",
    "    \n",
    "    my $x = $p->LP->MAXIMAL_VERTEX;\n",
    "    my $ctx = $p->LP->MAXIMAL_VALUE;\n",
    "    \n",
    "    print \"Relaxed solution: \", $x, \" -> \", $ctx,\"\\n\";\n",
    "\n",
    "\n",
    "### Pruning\n",
    "# And if that is already worse then the best integer solution so far,\n",
    "# we can prune this branch.\n",
    "    \n",
    "    if($ctx <= $z){\n",
    "        print \"is pruned, because <= $z\\n\";\n",
    "        return 0;\n",
    "    }\n",
    "\n",
    "\n",
    "### Check for integer solution\n",
    "# In the following loop, we find the first coordinate of the solution vector,\n",
    "# that is not integer.\n",
    "  \n",
    "    my $i = 0;\n",
    "    for(my $k=1; $k<$x->dim(); $k++){\n",
    "        if(f($x->[$k]) != 0){\n",
    "            $i = $k;\n",
    "        }\n",
    "    }\n",
    "\n",
    "\n",
    "#If we didn't find any, we have found an integer solution and possibly a new best solution.\n",
    "#In the case of an integer solution, we can stop here.\n",
    "    \n",
    "    if($i == 0){\n",
    "        print \"Solution is Integer.\\n\";\n",
    "        # Update the best solution\n",
    "        if($ctx > $z){\n",
    "            $x_best = $x;\n",
    "            $z = $ctx;\n",
    "            print \"Solution is better then the last optimum.\\n\";\n",
    "        }\n",
    "        return;\n",
    "    }\n",
    "\n",
    "\n",
    "### Branching\n",
    "# Now lets create some new inequalities.\n",
    "# At first round the not integer coordinate of x up and down.\n",
    "\n",
    "    #Round up and down:\n",
    "    my $x_i_down = $x->[$i] - f($x->[$i]);\n",
    "    my $x_i_up = $x_i_down + 1;\n",
    "\n",
    "\n",
    "# To generate P_1, add the inequality x_i <= x_i_down.\n",
    "  \n",
    "    my @ineq_to_add_1 = ($x_i_down);\n",
    "    for (my $k = 1; $k<$x->dim(); $k++){\n",
    "        if($k == $i){\n",
    "            push @ineq_to_add_1, -1;\n",
    "        }else{\n",
    "            push @ineq_to_add_1, 0;\n",
    "        }\n",
    "    }\n",
    "\n",
    "\n",
    "# To generate P_2, add the inequality x_i >= x_i_up.\n",
    "    \n",
    "    my @ineq_to_add_2 = (-$x_i_up);\n",
    "    for (my $k = 1; $k<$x->dim(); $k++){\n",
    "        if($k == $i){\n",
    "            push @ineq_to_add_2, 1;\n",
    "        }else{\n",
    "            push @ineq_to_add_2, 0;\n",
    "        }\n",
    "    }\n",
    "\n",
    "\n",
    "# Now add the new inequalities to the old ones ...\n",
    "       \n",
    "    my @new_ineqs_1 = @ineqs;\n",
    "    push @new_ineqs_1, \\@ineq_to_add_1;\n",
    "    \n",
    "    my @new_ineqs_2 = @ineqs;\n",
    "    push @new_ineqs_2, \\@ineq_to_add_2;\n",
    "\n",
    "    \n",
    "# And do the recursive calls.\n",
    "# Here I added some printouts, that give an inorder traversal of \n",
    "# the B&B tree and the edges that are used for that.\n",
    "\n",
    "    print \"Set x$i <= $x_i_down\\n\";\n",
    "    branchnbound($objective, @new_ineqs_1);\n",
    "    print \"Forget x$i <= $x_i_down\\n\";\n",
    "    \n",
    "    print \"Set x$i >= $x_i_up\\n\";\n",
    "    branchnbound($objective, @new_ineqs_2);\n",
    "    print \"Forget x$i >= $x_i_up\\n\";\n",
    "    \n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, the first call of the Routine:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Relaxed solution: 1 20/7 3 -> 59/7\n",
       "Set x1 <= 2\n",
       "Relaxed solution: 1 2 1/2 -> 15/2\n",
       "Set x2 <= 0\n",
       "Relaxed solution: 1 3/2 0 -> 6\n",
       "Set x1 <= 1\n",
       "Relaxed solution: 1 1 0 -> 4\n",
       "Solution is Integer.\n",
       "Solution is better then the last optimum.\n",
       "Forget x1 <= 1\n",
       "Set x1 >= 2\n",
       "EMPTY!\n",
       "Forget x1 >= 2\n",
       "Forget x2 <= 0\n",
       "Set x2 >= 1\n",
       "Relaxed solution: 1 2 1 -> 7\n",
       "Solution is Integer.\n",
       "Solution is better then the last optimum.\n",
       "Forget x2 >= 1\n",
       "Forget x1 <= 2\n",
       "Set x1 >= 3\n",
       "EMPTY!\n",
       "Forget x1 >= 3\n",
       "The solution is: 1 2 1 with the optimal value 7\n"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/html": [
       "<details><summary><pre style=\"display:inline\"><small>Click here for additional output</small></pre></summary>\n",
       "<pre>\n",
       "polymake: used package lrs\n",
       "  Implementation of the reverse search algorithm of Avis and Fukuda.\n",
       "  Copyright by David Avis.\n",
       "  http://cgm.cs.mcgill.ca/~avis/C/lrs.html\n",
       "\n",
       "</pre>\n",
       "</details>\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "@ineqs = ([14,-7,2],[3,0,-1],[3,-2,2],[0,1,0],[0,0,1]);\n",
    "$objective = [0,4,-1];\n",
    "branchnbound($objective, @ineqs);\n",
    "    \n",
    "print \"The solution is: $x_best with the optimal value $z\\n\";"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "polymake",
   "language": "polymake",
   "name": "polymake"
  },
  "language_info": {
   "codemirror_mode": "perl",
   "file_extension": ".pl",
   "mimetype": "text/x-polymake",
   "name": "polymake"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
