{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial was written by Michael Schmitt, a student of the course \"Discrete Optimization\".\n",
    "\n",
    "# Gomory Cuts\n",
    "\n",
    "In this Tutorial, I describe how to solve an ILP using the gomory cut approach implemented in 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": [
    "## 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",
    "The main Routine of this script gets invoked with the following parameters:\n",
    "\n",
    "\n",
    "*  The Matrix of equalities as an Matrix<Rational>\n",
    "\n",
    "*  The Matrix of inequalites as an Matrix<Rational>\n",
    "\n",
    "*  The Objective function as a list of Rationals.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "sub gomory {\n",
    "    \n",
    "        my $A = shift;\n",
    "        my $ineqs = shift;\n",
    "        my @obj_list = @_;\n",
    "        my $obj = new Vector<Rational>(@obj_list);\n",
    "           \n",
    "        my $p = new Polytope<Rational>;\n",
    "        $p->EQUATIONS = $A;\n",
    "        $p->INEQUALITIES = $ineqs;\n",
    "\n",
    "\n",
    "### Feasibility check\n",
    "# Since all the inequalities that we add are valid for the integer hull \n",
    "# of the polytope, we can conclude that the integer hull is empty,\n",
    "# when the examined polytope in any iteration cycle is empty.\n",
    "\n",
    "        if(!$p->FEASIBLE){\n",
    "            print \"The integer hull of P is empty.\\n\";\n",
    "            return;\n",
    "        }\n",
    "\n",
    "\n",
    "### Solving the relaxation\n",
    "\n",
    "        $p->LP = new LinearProgram<Rational>(LINEAR_OBJECTIVE=>$obj);\n",
    "    \n",
    "        my $x = $p->LP->MAXIMAL_VERTEX;\n",
    "        print \"Optimal solution of the relaxation: \", $x,\"\\n\";\n",
    "    \n",
    "    \n",
    "        my @B = ();\n",
    "        my @N = ();\n",
    "        for(my $k = 1; $k < $x->dim(); $k++){\n",
    "            if($x->[$k] > 0){\n",
    "                push @B, $k;\n",
    "            }else{\n",
    "                push @N, $k;\n",
    "            }\n",
    "        }\n",
    "\n",
    "   \n",
    "### Check for integer solution\n",
    "# If we found an integer solution in the linear relaxation,\n",
    "# it's the solution of the ILP aswell.\n",
    "# If not, we remember the coordinate that is not integer for later steps.\n",
    "\n",
    "        my $i = -1;\n",
    "        for(my $k=0; $k<@B; $k++){\n",
    "            if(f($x->[$B[$k]]) != 0){\n",
    "                $i = $k;\n",
    "            }\n",
    "        }\n",
    "        \n",
    "        if($i == -1){\n",
    "            print \"Found optimal integer solution: \", $x, \" -> \", $p->LP->MAXIMAL_VALUE,\"\\n\";\n",
    "            return;\n",
    "        }\n",
    "\n",
    "\n",
    "### Compute optimal basis\n",
    "# In this example, I only implemented the computation of a Basis\n",
    "# in the non-degenerated case (that is, where every Basis Variables are strictly positive.\n",
    "\n",
    "        my @A_b = ();\n",
    "        foreach my $b (@B){\n",
    "            push @A_b, $A->col($b);\n",
    "        }\n",
    "        \n",
    "        my $A_b = transpose(new Matrix<Rational>(@A_b));\n",
    "        my $A_inv = inv($A_b);\n",
    "            \n",
    "\n",
    "    \n",
    "### Compute new equality\n",
    "# Here is where the remembered non-integer variable comes into play,\n",
    "# to compute the Gomory cut itself.\n",
    "    \n",
    "        my @new_row;\n",
    "        my @new_col;\n",
    "        \n",
    "        for(my $j=0; $j<$A->rows(); $j++){\n",
    "            $new_col[$j] = 0;\n",
    "        }\n",
    "        \n",
    "        $new_row[0] = f($A_inv->row($i)*($A->col(0)));\n",
    "        foreach my $j (@N){\n",
    "            $new_row[$j] = (-1)*f((-1)*$A_inv->row($i)*$A->col($j));\n",
    "        }\n",
    "        foreach my $j (@B){\n",
    "            $new_row[$j] = 0;\n",
    "        }\n",
    "        push @new_row, 1;\n",
    "        my $new_row_vec = new Vector<Rational>(@new_row);\n",
    "        my $new_col_vec = new Vector<Rational>(@new_col);\n",
    "    \n",
    "        print \"New equality: $new_row_vec\\n\";      \n",
    "\n",
    "    \n",
    "### Compute new inequality\n",
    "# The new inequality demands, that the newly introduced slack variable will be positive.  \n",
    "        \n",
    "        my @new_ineq_col;    \n",
    "        for(my $j=0; $j<$ineqs->rows(); $j++){\n",
    "            $new_ineq_col[$j] = 0;\n",
    "        }\n",
    "        \n",
    "        my @new_ineq_row = @new_ineq_col;\n",
    "        #push @new_ineq_row, 0; #this leads to dimension mismatches\n",
    "        push @new_ineq_row, 1;\n",
    "        \n",
    "        my $new_ineq_row_vec = new Vector<Rational>(@new_ineq_row);\n",
    "        my $new_ineq_col_vec = new Vector<Rational>(@new_ineq_col);    \n",
    "\n",
    "    \n",
    "### Construct the new matrices\n",
    "# The two matrices constructed are the equality matrix A and the inequality matrix\n",
    "# that restricts the variables to be positive.\n",
    "# In both cases we add a new column on the right side and a new row at the bottom.\n",
    "        \n",
    "        my $new_A = new Matrix<Rational>(($A|$new_col_vec) / $new_row_vec); \n",
    "        my $new_ineqs = new Matrix<Rational>(($ineqs|$new_ineq_col_vec) / $new_ineq_row_vec);\n",
    "        \n",
    "        push @obj_list, 0;\n",
    "        \n",
    "        gomory($new_A, $new_ineqs, @obj_list);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### First call of the Function\n",
    "Finally we create two example matrices and an example objective function, to call the main routine.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Optimal solution of the relaxation: 1 20/7 3 0 0 23/7\n",
       "New equality: 5/7 0 0 -2/7 -4/7 0 1\n",
       "Optimal solution of the relaxation: 1 5/2 7/4 0 5/4 3/2 0\n",
       "New equality: 1/2 0 0 0 0 0 -1/2 1\n",
       "Optimal solution of the relaxation: 1 2 1/2 1 5/2 0 1 0\n",
       "New equality: 1/2 0 0 0 0 -1/2 0 0 1\n",
       "Optimal solution of the relaxation: 1 2 1 2 2 1 1 0 0\n",
       "Found optimal integer solution: 1 2 1 2 2 1 1 0 0 -> 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": [
    "$A = new Matrix<Rational>([\n",
    "[-14, 7, -2, 1, 0, 0],\n",
    "[-3, 0, 1, 0, 1, 0],\n",
    "[-3, 2, -2, 0, 0, 1]]);  \n",
    "\n",
    "$ineqs = new Matrix<Rational>([\n",
    "[0, 1, 0, 0, 0, 0],\n",
    "[0, 0, 1, 0, 0, 0],\n",
    "[0, 0, 0, 1, 0, 0],\n",
    "[0, 0, 0, 0, 1, 0],\n",
    "[0, 0, 0, 0, 0, 1]]);\n",
    "    \n",
    "@objective = (0,4,-1,0,0,0);\n",
    "\n",
    "gomory($A, $ineqs, @objective);"
   ]
  }
 ],
 "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
}
