001/*
002 * $Id: IntegerProgram.java 5734 2017-02-18 20:30:54Z kredel $
003 */
004
005package edu.jas.application;
006
007
008import java.io.IOException;
009import java.io.Reader;
010import java.io.StringReader;
011import java.util.Arrays;
012
013import org.apache.log4j.Logger;
014
015import edu.jas.arith.BigInteger;
016import edu.jas.poly.ExpVector;
017import edu.jas.poly.ExpVectorLong;
018import edu.jas.poly.GenPolynomial;
019import edu.jas.poly.GenPolynomialTokenizer;
020import edu.jas.poly.PolynomialList;
021import edu.jas.poly.TermOrder;
022
023
024/**
025 * Solution of Integer Programming problems using Groebner bases. Integer
026 * Program is in standard form -> minimization of given Equation plus
027 * restrictions. See chapter 8 in Cox, Little, O'Shea "Using Algebraic 
028 * Geometry", 1998.
029 * @author Maximilian Nohr
030 */
031public class IntegerProgram implements java.io.Serializable {
032
033
034    private static final Logger logger = Logger.getLogger(IntegerProgram.class);
035
036
037    private static boolean DEBUG = logger.isDebugEnabled(); //false;
038
039
040    private boolean negVars;
041
042
043    private boolean success;
044
045
046    /* 
047    Integer Program is in standard form -> minimization of given 
048    Equation + restrictions
049    */
050    int n; // # of variables including slack variables
051
052
053    int m; // # of restrictions
054
055
056    long[] C; // List of Coefficients c_1...c_n of objective function
057
058
059    long[] B; // List of  b_1...b_m, restriction right hand side  
060
061
062    long[][] A; // m x n Matrix of a_{11}....a_{mn}, restriction matrix
063
064
065    long[] D; // Polynomial degrees 1...n
066
067
068    long[][] Aa; // restriction matrix a_{11}..a_{mn} after Laurent transformation
069
070
071    Ideal<BigInteger> I; //the Ideal
072
073
074    Ideal<BigInteger> GB; // the Groebner base for the ideal
075
076
077    TermOrder to; // the Term order for the GB
078
079
080    PolynomialList<BigInteger> F; // The Polynomials that generate the Ideal
081
082
083    GenPolynomial<BigInteger> S; // The Polynomial that is reduced for the Solution
084
085
086    /**
087     * Constructor. Use one instance for every new problem since solve() is not
088     * reentrant.
089     */
090    public IntegerProgram() {
091    }
092
093
094    /**
095     * Set DEBUG flag to parameter value.
096     * @param b
097     */
098    public void setDebug(boolean b) {
099        DEBUG = b;
100    }
101
102
103    /*
104     * Setup the Ideal corresponding to the Integer Program. 
105     */
106    @SuppressWarnings("cast")
107    private void createIdeal() {
108        Aa = A.clone();
109        negVars = negVarTest();
110        String[] w = new String[n];
111        String[] f = new String[n];
112        String[] z = new String[m];
113        String[] t = new String[n];
114        StringBuilder sb = new StringBuilder();
115        sb.append("Int(");
116
117        if (negVars) { //A or B has negative values
118            for (int i = 1; i <= n; i++) {
119                w[i - 1] = "w" + i;
120            }
121            for (int i = 1; i <= m; i++) {
122                z[i - 1] = "z" + i;
123            }
124            for (int i = 0; i < n; i++) {
125                StringBuffer h = new StringBuffer("");
126                long min = 0;
127                for (int j = 0; j < m; j++) {
128                    if (A[j][i] < min) {
129                        min = A[j][i];
130                    }
131                }
132                if (min < 0) {
133                    long e = -min;
134                    h.append("t^" + e + " * ");
135                    for (int j = 0; j < m; j++) {
136                        Aa[j][i] = A[j][i] + e;
137                        h.append(z[j] + "^" + Aa[j][i] + " * ");
138                    }
139                } else {
140                    for (int j = 0; j < m; j++) {
141                        if (A[j][i] != 0) {
142                            h.append(z[j] + "^" + A[j][i] + " * ");
143                        }
144                    }
145                }
146                f[i] = h.substring(0, h.length() - 3).toString();
147            }
148            setDeg();
149            setTO();
150            for (int i = 0; i < n; i++) {
151                t[i] = f[i] + " - " + w[i];
152            }
153            sb.append("t");
154            for (int i = 0; i < m; i++) {
155                sb.append(",").append(z[i]);
156            }
157            for (int i = 0; i < n; i++) {
158                sb.append(",").append(w[i]);
159            }
160            sb.append(") W ");
161            //sb.append(to.weightToString().substring(6, to.weightToString().length()));
162            sb.append(to.weightToString());
163            sb.append(" ( ( t");
164            for (int i = 0; i < m; i++) {
165                sb.append(" * ").append(z[i]);
166            }
167            sb.append(" - 1 )");
168            for (int i = 0; i < n; i++) {
169                sb.append(", (").append(t[i]).append(" )");
170            }
171            sb.append(") ");
172
173        } else { //if neither A nor B contain negative values
174            for (int i = 1; i <= n; i++) {
175                w[i - 1] = "w" + i;
176            }
177            for (int i = 1; i <= m; i++) {
178                z[i - 1] = "z" + i;
179            }
180            for (int i = 0; i < n; i++) {
181                StringBuffer h = new StringBuffer("");
182                for (int j = 0; j < m; j++) {
183                    if (A[j][i] != 0) {
184                        h.append(z[j] + "^" + A[j][i] + " * ");
185                    }
186                }
187                f[i] = h.substring(0, h.length() - 3).toString();
188            }
189            setDeg();
190            setTO();
191            for (int i = 0; i < n; i++) {
192                t[i] = f[i] + " - " + w[i];
193            }
194            sb.append(z[0]);
195            for (int i = 1; i < m; i++) {
196                sb.append(",").append(z[i]);
197            }
198            for (int i = 0; i < n; i++) {
199                sb.append(",").append(w[i]);
200            }
201            sb.append(") W ");
202            //sb.append(to.weightToString().substring(6, to.weightToString().length()));
203            sb.append(to.weightToString());
204            sb.append(" ( (").append(t[0]).append(")");
205            for (int i = 1; i < n; i++) {
206                sb.append(", (").append(t[i]).append(" )");
207            }
208            sb.append(") ");
209        }
210        if (DEBUG) {
211            logger.debug("list=" + sb.toString());
212        }
213
214        Reader source = new StringReader(sb.toString());
215        GenPolynomialTokenizer parser = new GenPolynomialTokenizer(source);
216        PolynomialList<BigInteger> F = null;
217
218        try {
219            F = (PolynomialList<BigInteger>) parser.nextPolynomialSet();
220        } catch (ClassCastException e) {
221            e.printStackTrace();
222            return;
223        } catch (IOException e) {
224            e.printStackTrace();
225            return;
226        }
227        if (DEBUG) {
228            logger.debug("F=" + F);
229        }
230        I = new Ideal<BigInteger>(F);
231        return;
232    }
233
234
235    /**
236     * @return true if the last calculation had a solution, else false
237     */
238    public boolean getSuccess() {
239        return success;
240    }
241
242
243    /**
244     * Solve Integer Program.
245     * @param A matrix of restrictions
246     * @param B restrictions right hand side
247     * @param C objective function
248     * @return solution s, such that s*C -&gt; minimal and A*s = B, or s = null
249     *         if no solution exists
250     */
251    public long[] solve(long[][] A, long[] B, long[] C) {
252        this.A = Arrays.copyOf(A, A.length);
253        this.B = Arrays.copyOf(B, B.length);
254        this.C = Arrays.copyOf(C, C.length);
255        this.n = A[0].length;
256        this.m = A.length;
257        D = new long[n];
258
259        createIdeal();
260        GB = I.GB();
261        return solve(B);
262    }
263
264
265    /**
266     * Solve Integer Program for new right hand side. Uses the GB (matrix A and
267     * C) from the last calculation.
268     * @param B restrictions right hand side
269     * @return solution s, such that s*C -&gt; minimal and A*s = B, or s = null
270     *         if no solution exists
271     */
272    public long[] solve(long[] B) {
273        long[] returnMe = new long[n];
274        if (B.length != m) {
275            System.out.println("ERROR: Dimensions don't match: " + B.length + " != " + m);
276            return returnMe;
277        }
278        long[] l;
279        this.B = Arrays.copyOf(B, B.length);
280        if (DEBUG) {
281            logger.debug("GB=" + GB);
282        }
283        if (negVars) {
284            l = new long[m + n + 1];
285            long min = findMin(B);
286            if (min < 0) {
287                long r = -min;
288                l[m + n] = r;
289                for (int i = 0; i < m; i++) {
290                    l[m + n - 1 - i] = B[i] + r;
291                }
292            } else {
293                for (int i = 0; i < m; i++) {
294                    l[m + n - 1 - i] = B[i];
295                }
296            }
297        } else {
298            l = new long[m + n];
299            for (int i = 0; i < m; i++) {
300                l[m + n - 1 - i] = B[i];
301            }
302        }
303        ExpVector e = new ExpVectorLong(l);
304        S = new GenPolynomial<BigInteger>(I.getRing(), e);
305        S = GB.normalform(S);
306
307        ExpVector E = S.exponentIterator().next();
308        for (int i = 0; i < n; i++) {
309            returnMe[n - 1 - i] = E.getVal(i);
310        }
311        success = true;
312        for (int i = n; i < n + m; i++) {
313            if (E.getVal(i) != 0) {
314                success = false;
315                break;
316            }
317        }
318        if (success) {
319            if (DEBUG) {
320                logger.debug("The solution is: " + Arrays.toString(returnMe));
321            }
322        } else {
323            logger.warn("The Problem does not have a feasible solution.");
324            returnMe = null;
325        }
326        return returnMe;
327    }
328
329
330    /*
331     * Set the degree.
332     */
333    private void setDeg() {
334        for (int j = 0; j < n; j++) {
335            for (int i = 0; i < m; i++) {
336                D[j] += Aa[i][j];
337            }
338        }
339    }
340
341
342    /*
343     * Set the term order.
344     */
345    private void setTO() {
346        int h;
347        if (negVars) {//if A and/or B contains negative values
348            h = m + n + 1;
349        } else {
350            h = m + n;
351        }
352        long[] u1 = new long[h];
353        long[] u2 = new long[h];
354        for (int i = 0; i < h - n; i++) { //m+1 because t needs another 1 
355            u1[h - 1 - i] = 1;
356        }
357        long[] h1 = new long[h]; // help vectors to construct u2 out of
358        long[] h2 = new long[h];
359        for (int i = h - n; i < h; i++) {
360            h1[i] = C[i - (h - n)];
361            h2[i] = D[i - (h - n)];
362        }
363        long min = h1[0];
364        for (int i = 0; i < h; i++) {
365            u2[h - 1 - i] = h1[i] + h2[i];
366            if (u2[h - 1 - i] < min) {
367                min = u2[h - 1 - i];
368            }
369        }
370        while (min < 0) {
371            min = u2[0];
372            for (int i = 0; i < h; i++) {
373                u2[h - 1 - i] += h2[i];
374                if (u2[h - 1 - i] < min) {
375                    min = u2[h - 1 - i];
376                }
377            }
378        }
379        long[][] wv = { u1, u2 };
380        to = new TermOrder(wv);
381    }
382
383
384    /*
385     * @see java.lang.Object#toString()
386     */
387    @Override
388    public String toString() {
389        StringBuilder sb = new StringBuilder();
390        sb.append("Function to minimize:\n");
391
392        char c = 'A'; // variables are named A, B, C, ...
393
394        boolean plus = false;
395        for (int i = 0; i < n; i++) {
396            if (C[i] != 0) {
397                if (C[i] < 0) {
398                    sb.append("(").append(C[i]).append(")");
399                    sb.append("*");
400                } else if (C[i] != 1) {
401                    sb.append(C[i]);
402                    sb.append("*");
403                }
404                sb.append(c);
405                sb.append(" + ");
406                plus = true;
407            }
408            c++;
409        }
410        if (plus) {
411            sb.delete(sb.lastIndexOf("+"), sb.length());
412        }
413        sb.append("\nunder the Restrictions:\n");
414        for (int i = 0; i < m; i++) {
415            c = 'A';
416            //System.out.println("A["+i+"] = " + Arrays.toString(A[i]));
417            plus = false;
418            for (int j = 0; j < n; j++) {
419                if (A[i][j] != 0) {
420                    if (A[i][j] < 0) {
421                        sb.append("(").append(A[i][j]).append(")");
422                        sb.append("*");
423                    } else if (A[i][j] != 1) {
424                        sb.append(A[i][j]);
425                        sb.append("*");
426                    } 
427                    sb.append(c);
428                    sb.append(" + ");
429                    plus = true;
430                }
431                c++;
432            }
433            if (plus) {
434                sb.delete(sb.lastIndexOf("+"), sb.length()); 
435            } else {
436                sb.append("0 ");
437            }
438            sb.append("= ").append(B[i]).append("\n");
439        }
440        return sb.toString();
441    }
442
443
444    /*
445     * Test for negative variables.
446     * @return true if negative variables appear
447     */
448    private boolean negVarTest() {
449        for (int i = 0; i < m; i++) {
450            if (B[i] < 0) {
451                return true;
452            }
453            for (int j = 0; j < n; j++) {
454                if (A[i][j] < 0) {
455                    return true;
456                }
457
458            }
459        }
460        return false;
461    }
462
463
464    /*
465     * Find minimal element.
466     * @param B vector of at least one element, B.length &gt;= 1 
467     * @return minimal element of B
468     */
469    private long findMin(long[] B) {
470        long min = B[0];
471        for (int i = 1; i < B.length; i++) {
472            if (B[i] < min) {
473                min = B[i];
474            }
475        }
476        return min;
477    }
478
479}