All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ojalgo.optimisation.FileFormatMPS Maven / Gradle / Ivy

Go to download

oj! Algorithms - ojAlgo - is Open Source Java code that has to do with mathematics, linear algebra and optimisation.

There is a newer version: 55.0.1
Show newest version
/*
 * Copyright 1997-2024 Optimatika
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.ojalgo.optimisation;

import static org.ojalgo.function.constant.BigMath.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.ojalgo.netio.ASCII;

/**
 * Mathematical Programming System (MPS) parser
 *
 * @author apete
 */
final class FileFormatMPS {

    /**
     * BoundType used with the BOUNDS section.
     *
     * 
     *  type            meaning
     * ---------------------------------------------------
     *   LO    lower bound        b <= x (< +inf)
     *   UP    upper bound        (0 <=) x <= b
     *   FX    fixed variable     x = b
     *   FR    free variable      -inf < x < +inf
     *   MI    lower bound -inf   -inf < x (<= 0)
     *   PL    upper bound +inf   (0 <=) x < +inf
     *   BV    binary variable    x = 0 or 1
     *   LI    integer variable   b <= x (< +inf)
     *   UI    integer variable   (0 <=) x <= b
     *   SC    semi-cont variable x = 0 or l <= x <= b
     *         l is the lower bound on the variable
     *         If none set then defaults to 1
     * 
* * @author apete */ enum BoundType { BV, FR, FX, LI, LO, MI, PL, SC, UI, UP; } final class Column { private boolean mySemicontinuous = false; private final Variable myVariable; Column(final String name) { super(); myVariable = myModel.newVariable(name); this.bound(BoundType.PL, null); } Column bound(final BoundType type, final BigDecimal value) { switch (type) { case LO: myVariable.lower(value); break; case UP: myVariable.upper(value); if (!myVariable.isLowerLimitSet()) { myVariable.lower(ZERO); } break; case FX: myVariable.level(value); break; case FR: myVariable.level(null); break; case MI: myVariable.lower(null); if (!myVariable.isUpperLimitSet()) { myVariable.upper(ZERO); } break; case PL: myVariable.upper(null); if (!myVariable.isLowerLimitSet()) { myVariable.lower(ZERO); } break; case BV: myVariable.lower(ZERO).upper(ONE).integer(true); break; case LI: myVariable.lower(value).upper(null).integer(true); break; case UI: myVariable.upper(value).integer(true); if (!myVariable.isLowerLimitSet()) { myVariable.lower(ZERO); } break; case SC: mySemicontinuous = true; myVariable.upper(value); if (!myVariable.isLowerLimitSet()) { myVariable.lower(ONE); } break; default: break; } return this; } /** * @return the variable */ Variable getVariable() { return myVariable; } Column integer(final boolean flag) { myVariable.setInteger(flag); return this; } boolean isSemicontinuous() { return mySemicontinuous; } void setRowValue(final String rowName, final BigDecimal value) { Row row = myRows.get(rowName); Expression expression = row.getExpression(); expression.set(myVariable, value); } } /** * @author apete */ enum ColumnMarker { INTEND, INTORG; } interface FieldPredicate { FieldPredicate BOUND_TYPE = (line, start, index, field) -> field != null && field.length() == 2; FieldPredicate COLUMN_NAME = (line, start, index, field) -> { if (field == null || Math.max(field.length(), index - start) < 8) { return false; } return true; }; FieldPredicate EMPTY = (line, start, index, field) -> { if (field == null || field.length() == 0) { return true; } return false; }; FieldPredicate NOT_USED = (line, start, index, field) -> false; FieldPredicate NUMBER = (line, start, index, field) -> field.length() > 0; FieldPredicate ROW_NAME = (line, start, index, field) -> { if (field.length() <= 0) { return false; } for (int i = index + 1; i < line.length(); i++) { if (!ASCII.isSpace(line.charAt(i))) { return false; } } return true; }; FieldPredicate ROW_TYPE = (line, start, index, field) -> field != null && field.length() == 1; /** * Test if the field is "correct". * * @param line The full line being parsed * @param start The styart index of the current field as specified in the original MPS format * @param index The current index of that full line * @param field The part of the line that being investigated * @return true if the field is correct/complete */ boolean test(String line, int start, int index, String field); } enum FileSection { BOUNDS, COLUMNS, ENDATA, NAME, OBJNAME, OBJSENSE, QMATRIX, QUADOBJ, RANGES, RHS, ROWS, SOS; } final class Row { private final Expression myExpression; private final RowType myType; Row(final String name, final RowType rowType, final String objName) { super(); myExpression = myModel.newExpression(name); myType = rowType; if (myType == RowType.N && name.equals(objName)) { myExpression.weight(ONE); } else { myExpression.weight(null); // 0.0 is the default RHS value this.rhs(ZERO); } } /** * @return the expression */ Expression getExpression() { return myExpression; } /** * @return the type */ RowType getType() { return myType; } Row range(final BigDecimal value) { switch (myType) { case E: int tmpSignum = value.signum(); if (tmpSignum == 1) { myExpression.upper(myExpression.getLowerLimit().add(value)); } else if (tmpSignum == -1) { myExpression.lower(myExpression.getUpperLimit().add(value)); } break; case L: myExpression.lower(myExpression.getUpperLimit().subtract(value.abs())); break; case G: myExpression.upper(myExpression.getLowerLimit().add(value.abs())); break; default: break; } return this; } Row rhs(final BigDecimal value) { switch (myType) { case E: myExpression.level(value); break; case L: myExpression.upper(value); break; case G: myExpression.lower(value); break; case N: myExpression.addObjectiveConstant(value.negate()); break; default: break; } return this; } void setColumnValue(final String columnName, final BigDecimal value) { myExpression.set(myColumns.get(columnName).getVariable(), value); } } /** * RowType used with the ROWS and RANGES sections. * *
     * type      meaning
     * ---------------------------
     *  E    equality
     *  L    less than or equal
     *  G    greater than or equal
     *  N    objective
     *  N    no restriction
     *
     * row type       sign of r       h          u
     * ----------------------------------------------
     *    G            + or -         b        b + |r|
     *    L            + or -       b - |r|      b
     *    E              +            b        b + |r|
     *    E              -          b - |r|      b
     * 
* * @author apete */ enum RowType { E, G, L, N; } private static final String COMMENT = "*"; /** * Seems to be used in problem headers/comment to mark references to authors and such */ private static final String COMMENT_REF = "&"; private static final int[] FIELD_START = new int[] { 1, 4, 14, 24, 39, 49, 64 }; private static final String INTEND = "INTEND"; private static final String INTORG = "INTORG"; private static final String MARKER = "MARKER"; private static final String MAX = "MAX"; private static final String SPACE = " "; static ExpressionsBasedModel read(final InputStream input) { FileFormatMPS retVal = new FileFormatMPS(); String line; FileSection section = null; try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) { while ((line = reader.readLine()) != null) { if (line.length() == 0 || line.startsWith(COMMENT) || line.startsWith(COMMENT_REF)) { // Skip this line } else if (line.startsWith(SPACE)) { retVal.parseSectionLine(section, line); } else { section = retVal.identifySection(line); } } } catch (IOException cause) { throw new RuntimeException(cause); } return retVal.getModel(); } private final Map myRows = new HashMap<>(); private final Map myColumns = new HashMap<>(); private final FieldPredicate myExistingColumn = (line, start, index, field) -> myColumns.containsKey(field); private final FieldPredicate myExistingRow = (line, start, index, field) -> myRows.containsKey(field); private final String[] myFields = new String[6]; private String myIdBOUNDS = null; private String myIdRANGES = null; private String myIdRHS = null; private String myIdRowN = null; private boolean myIntegerMarker = false; private final FieldPredicate myMatchingBOUNDS = (line, start, index, field) -> { if (myIdBOUNDS != null) { if (myIdBOUNDS.equals(field)) { return true; } return false; } return this.nameColumns(line, field); }; private final FieldPredicate myMatchingRANGES = (line, start, index, field) -> { if (myIdRANGES != null) { if (myIdRANGES.equals(field)) { return true; } return false; } return this.nameRows(line, field); }; private final FieldPredicate myMatchingRHS = (line, start, index, field) -> { if (myIdRHS != null) { if (myIdRHS.equals(field)) { return true; } return false; } return this.nameRows(line, field); }; private final ExpressionsBasedModel myModel; private String myName; private Expression myQuadObjExpr = null; private final FieldPredicate[] myVerifierBOUNDS; private final FieldPredicate[] myVerifierCOLUMNS; private final FieldPredicate[] myVerifierQ; private final FieldPredicate[] myVerifierRANGES; private final FieldPredicate[] myVerifierRHS; private final FieldPredicate[] myVerifierROWS; FileFormatMPS() { super(); myModel = new ExpressionsBasedModel(); myVerifierROWS = new FieldPredicate[] { FieldPredicate.ROW_TYPE, FieldPredicate.ROW_NAME, FieldPredicate.NOT_USED, FieldPredicate.NOT_USED, FieldPredicate.NOT_USED, FieldPredicate.NOT_USED }; myVerifierCOLUMNS = new FieldPredicate[] { FieldPredicate.EMPTY, FieldPredicate.COLUMN_NAME, myExistingRow, FieldPredicate.NUMBER, myExistingRow, FieldPredicate.NUMBER }; myVerifierRHS = new FieldPredicate[] { FieldPredicate.EMPTY, myMatchingRHS, myExistingRow, FieldPredicate.NUMBER, myExistingRow, FieldPredicate.NUMBER }; myVerifierRANGES = new FieldPredicate[] { FieldPredicate.EMPTY, myMatchingRANGES, myExistingRow, FieldPredicate.NUMBER, myExistingRow, FieldPredicate.NUMBER }; myVerifierBOUNDS = new FieldPredicate[] { FieldPredicate.BOUND_TYPE, myMatchingBOUNDS, myExistingColumn, FieldPredicate.NUMBER, myExistingColumn, FieldPredicate.NUMBER }; myVerifierQ = new FieldPredicate[] { FieldPredicate.EMPTY, myExistingColumn, myExistingColumn, FieldPredicate.NUMBER, FieldPredicate.NOT_USED, FieldPredicate.NOT_USED }; } @Override public String toString() { return myModel.toString(); } private void extractFields(final String line, final FieldPredicate[] verifiers) { char tecken; int first = -1; int limit = -1; boolean word = false; for (int i = 1, length = line.length(), f = 0; i < length; i++) { tecken = line.charAt(i); if (i == 4) { f = Math.max(f, 1); } else if (!word && i == 14) { f = Math.max(f, 2); } if (!word && !ASCII.isSpace(tecken)) { word = true; first = i; } else if (word && ASCII.isSpace(tecken)) { word = false; limit = i; } if (word && i + 1 == length) { word = false; limit = i + 1; } if (limit > first) { String field = line.substring(first, limit); if (!verifiers[f].test(line, FIELD_START[f], i, field)) { word = true; } else { myFields[f++] = field; first = -1; } limit = -1; } } } private ExpressionsBasedModel getModel() { return myModel; } private FileSection identifySection(final String line) { int tmpSplit = line.indexOf(SPACE); String tmpSection; String tmpArgument; if (tmpSplit != -1) { tmpSection = line.substring(0, tmpSplit).trim(); tmpArgument = line.substring(tmpSplit).trim(); } else { tmpSection = line.trim(); tmpArgument = ""; } FileSection retVal = FileSection.valueOf(tmpSection); switch (retVal) { case NAME: myName = tmpArgument; break; default: break; } return retVal; } private void parseSectionLine(final FileSection section, final String line) { Arrays.fill(myFields, null); switch (section) { case NAME: break; case OBJSENSE: if (line.contains(MAX)) { myModel.setOptimisationSense(Optimisation.Sense.MAX); } else { myModel.setOptimisationSense(Optimisation.Sense.MIN); } break; case OBJNAME: break; case ROWS: this.extractFields(line, myVerifierROWS); RowType rowType = RowType.valueOf(myFields[0]); String rowName = myFields[1].trim(); if (myIdRowN == null && rowType == RowType.N) { myIdRowN = rowName; } myRows.put(myFields[1], new Row(rowName, rowType, myIdRowN)); break; case COLUMNS: if (line.contains(MARKER)) { if (line.contains(INTORG)) { myIntegerMarker = true; } else if (line.contains(INTEND)) { myIntegerMarker = false; } } else { this.extractFields(line, myVerifierCOLUMNS); Column tmpColumn = myColumns.computeIfAbsent(myFields[1].trim(), Column::new); tmpColumn.setRowValue(myFields[2], new BigDecimal(myFields[3])); if (myFields[4] != null) { tmpColumn.setRowValue(myFields[4], new BigDecimal(myFields[5])); } if (myIntegerMarker) { tmpColumn.integer(myIntegerMarker); } } break; case RHS: this.extractFields(line, myVerifierRHS); if (myIdRHS == null) { myIdRHS = myFields[1]; } else if (!myIdRHS.equals(myFields[1])) { break; } myRows.get(myFields[2]).rhs(new BigDecimal(myFields[3])); if (myFields[4] != null) { myRows.get(myFields[4]).rhs(new BigDecimal(myFields[5])); } break; case RANGES: this.extractFields(line, myVerifierRANGES); if (myIdRANGES == null) { myIdRANGES = myFields[1]; } else if (!myIdRANGES.equals(myFields[1])) { break; } myRows.get(myFields[2]).range(new BigDecimal(myFields[3])); if (myFields[4] != null) { myRows.get(myFields[4]).range(new BigDecimal(myFields[5])); } break; case BOUNDS: this.extractFields(line, myVerifierBOUNDS); if (myIdBOUNDS == null) { myIdBOUNDS = myFields[1]; } else if (!myIdBOUNDS.equals(myFields[1])) { break; } BoundType boundType = BoundType.valueOf(myFields[0]); myColumns.get(myFields[2]).bound(boundType, myFields[3] != null ? new BigDecimal(myFields[3]) : null); break; case QUADOBJ: this.extractFields(line, myVerifierQ); if (myQuadObjExpr == null) { myQuadObjExpr = myModel.newExpression(section.name()).weight(HALF); } Variable var1 = myColumns.get(myFields[1]).getVariable(); Variable var2 = myColumns.get(myFields[2]).getVariable(); BigDecimal param3 = new BigDecimal(myFields[3]); myQuadObjExpr.set(var1, var2, param3); if (!var1.equals(var2)) { myQuadObjExpr.set(var2, var1, param3); } break; case QMATRIX: this.extractFields(line, myVerifierQ); if (myQuadObjExpr == null) { myQuadObjExpr = myModel.newExpression(section.name()).weight(HALF); } Variable varA = myColumns.get(myFields[1]).getVariable(); Variable varB = myColumns.get(myFields[2]).getVariable(); BigDecimal paramC = new BigDecimal(myFields[3]); myQuadObjExpr.set(varA, varB, paramC); break; case ENDATA: break; default: break; } } boolean nameColumns(final String line, final String field) { String[] parts = line.split("\\s+"); if (parts.length == 7 && field.equals(parts[parts.length - 5]) && myColumns.containsKey(parts[parts.length - 4]) && myColumns.containsKey(parts[parts.length - 2])) { return true; } if (parts.length == 5 && field.equals(parts[parts.length - 3]) && myColumns.containsKey(parts[parts.length - 2])) { return true; } if (parts.length == 6 && myColumns.containsKey(parts[parts.length - 4]) && myColumns.containsKey(parts[parts.length - 2])) { return true; } if (parts.length == 4 && myColumns.containsKey(parts[parts.length - 2])) { return true; } return line.substring(FIELD_START[1], FIELD_START[2]).trim().equals(field); } boolean nameRows(final String line, final String field) { String[] parts = line.split("\\s+"); if (parts.length == 6 && field.equals(parts[parts.length - 5]) && myRows.containsKey(parts[parts.length - 4]) && myRows.containsKey(parts[parts.length - 2])) { return true; } if (parts.length == 4 && field.equals(parts[parts.length - 3]) && myRows.containsKey(parts[parts.length - 2])) { return true; } if (parts.length == 5 && myRows.containsKey(parts[parts.length - 4]) && myRows.containsKey(parts[parts.length - 2])) { return true; } if (parts.length == 3 && myRows.containsKey(parts[parts.length - 2])) { return true; } return line.substring(FIELD_START[1], FIELD_START[2]).trim().equals(field); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy