com.cloudera.branchreduce.onezero.SimplifiedLpParser.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudera.branchreduce.onezero.SimplifiedLpParser.java

Source

/**
 * Copyright (c) 2012, Cloudera, Inc. All Rights Reserved.
 *
 * Cloudera, Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"). You may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for
 * the specific language governing permissions and limitations under the
 * License.
 */
package com.cloudera.branchreduce.onezero;

import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import com.google.common.base.CharMatcher;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;

/**
 *
 */
public class SimplifiedLpParser {

    private final List<String> fileContents;

    private Expression objective;
    private List<Constraint> constraints;
    private List<String> variablePositions;

    public SimplifiedLpParser(String fileData) {
        this.fileContents = Lists.newArrayList(Splitter.on("\n").split(fileData));
    }

    public SimplifiedLpParser(File file) throws IOException {
        this.fileContents = Files.readLines(file, Charsets.UTF_8);
    }

    public Expression getObjective() {
        return objective;
    }

    public List<Constraint> getConstraints() {
        return constraints;
    }

    private enum ParseState {
        START, OBJECTIVE, CONSTRAINTS, END
    }

    private static class ConstraintData {
        private final Map<String, Integer> coefs;
        private final Integer bound;

        public ConstraintData(Map<String, Integer> coefs, Integer bound) {
            this.coefs = coefs;
            this.bound = bound;
        }

        public Constraint toConstraint(Map<String, Integer> variableIndices) {
            return new Constraint(create(coefs, variableIndices), bound);
        }
    }

    private static class ProblemData {
        private static Comparator<String> VAR_NAME_COMPARATOR = new Comparator<String>() {
            @Override
            public int compare(String v1, String v2) {
                int v1Start = CharMatcher.DIGIT.indexIn(v1);
                int v2Start = CharMatcher.DIGIT.indexIn(v2);
                if (v1.substring(0, v1Start).equals(v2.substring(0, v2Start))) {
                    Integer i1 = Integer.valueOf(v1.substring(v1Start));
                    Integer i2 = Integer.valueOf(v2.substring(v2Start));
                    return i1 - i2;
                } else {
                    return v1.compareTo(v2);
                }
            }
        };

        private boolean maximize = false;
        private Set<String> variables = Sets.newTreeSet(VAR_NAME_COMPARATOR);
        private Map<String, Integer> objectiveCoefs = null;
        private List<ConstraintData> constraints = Lists.newArrayList();

        public Set<String> getVariables() {
            return variables;
        }

        public Expression getObjective(Map<String, Integer> variableIndices) {
            return create(objectiveCoefs, variableIndices);
        }

        public List<Constraint> getConstraints(Map<String, Integer> variableIndices) {
            List<Constraint> c = Lists.newArrayList();
            for (ConstraintData cd : constraints) {
                c.add(cd.toConstraint(variableIndices));
            }
            return c;
        }

        public void setMaximize(boolean maximize) {
            this.maximize = maximize;
        }

        public void setObjectiveCoefs(Map<String, Integer> coefs) {
            if (maximize) {
                // Convert to a minimization problem
                this.objectiveCoefs = Maps.newHashMap();
                for (Map.Entry<String, Integer> e : coefs.entrySet()) {
                    objectiveCoefs.put(e.getKey(), -e.getValue());
                }
            } else {
                this.objectiveCoefs = ImmutableMap.copyOf(coefs);
            }
        }

        LineState parseCoefs(Map<String, Integer> currentCoefs, String line, LineState lastLineState) {
            int sign = lastLineState.lastSign;
            boolean limit = false;
            StringTokenizer st = new StringTokenizer(line, "+-<>=", true);
            while (st.hasMoreTokens()) {
                String token = st.nextToken().trim();
                if (token.equals("+")) {
                    sign = 1;
                } else if (token.equals("-")) {
                    sign = -1;
                } else if (token.equals("<") || token.equals("=") || token.equals(">")) {
                    limit = true;
                    break;
                } else if (!token.isEmpty()) {
                    parseVariable(currentCoefs, token, sign, lastLineState);
                    sign = 1;
                }
            }
            return lastLineState.update(sign, limit);
        }

        private void parseVariable(Map<String, Integer> currentCoefs, String token, int sign,
                LineState lastLineState) {
            int splitAt = -1;
            for (int i = 0; i < token.length(); i++) {
                char c = token.charAt(i);
                if (!Character.isDigit(c) && c != '.' && !((c == 'e' || c == 'E')
                        && (i < token.length() - 1 && Character.isDigit(token.charAt(i + 1))))) {
                    splitAt = i;
                    break;
                }
            }
            if (splitAt < 0) {
                throw new IllegalStateException(
                        String.format("Parse error for token = %s at line %d", token, lastLineState.lineNumber));
            }
            String variable = token.substring(splitAt);
            Integer coef = splitAt == 0 ? 1 : Integer.valueOf(token.substring(0, splitAt));
            variables.add(variable);
            currentCoefs.put(variable, sign * coef);
        }

        public void addConstraint(Map<String, Integer> currentCoefs, int constraintValue, boolean isUpperBound) {
            if (isUpperBound) {
                // Reverse the sense by negating everything.
                Map<String, Integer> negatedCoefs = Maps.newHashMap();
                for (Map.Entry<String, Integer> e : currentCoefs.entrySet()) {
                    negatedCoefs.put(e.getKey(), -e.getValue());
                }
                constraints.add(new ConstraintData(negatedCoefs, -constraintValue));
            } else {
                constraints.add(new ConstraintData(currentCoefs, constraintValue));
            }
        }
    }

    public void parse() {
        Iterator<String> lines = fileContents.iterator();
        ParseState state = ParseState.START;

        // Global variable info.
        ProblemData problem = new ProblemData();

        // Maps variables to coefficients for the current line.
        Map<String, Integer> currentCoefs = Maps.newHashMap();
        LineState lineState = new LineState();

        while (lines.hasNext() && state != ParseState.END) {
            String line = lines.next();
            int commentIndex = line.indexOf("\\");
            if (commentIndex > -1) {
                line = line.substring(0, commentIndex);
            }
            line = line.trim();

            int colonIndex = line.indexOf(':');
            if (colonIndex > -1) {
                // Ignore constraint names for now
                line = line.substring(colonIndex + 1);
            }

            if (line.length() > 0) {
                if (line.equalsIgnoreCase("end")) {
                    state = ParseState.END;
                    break;
                } else {
                    String cmd = line.toLowerCase();
                    switch (state) {
                    case START:
                        // Anything starting w/'min' means minimize.
                        if (cmd.startsWith("min")) {
                            problem.setMaximize(false);
                        } else {
                            problem.setMaximize(true);
                        }
                        state = ParseState.OBJECTIVE;
                        break;
                    case OBJECTIVE:
                        if (cmd.equals("subject to") || cmd.equals("st") || cmd.equals("s.t.")) {
                            problem.setObjectiveCoefs(currentCoefs);
                            currentCoefs = Maps.newHashMap();
                            state = ParseState.CONSTRAINTS;
                        } else {
                            lineState = problem.parseCoefs(currentCoefs, line, lineState);
                            if (lineState.hasLimit) {
                                throw new IllegalStateException(
                                        "Objective should not a bound (e.g., <=, >=, etc.)");
                            }
                        }
                        break;
                    case CONSTRAINTS:
                        lineState = problem.parseCoefs(currentCoefs, line, lineState);
                        if (lineState.hasLimit) {
                            // Means we hit a boundary and need to update.
                            String[] pieces = line.split("=");
                            if (pieces.length != 2) {
                                throw new IllegalStateException("Could not parse line: " + line);
                            }
                            int constraintValue = Integer.valueOf(pieces[1].trim());
                            char bound = pieces[0].charAt(pieces[0].length() - 1);
                            if (bound != '<' && bound != '>') {
                                throw new IllegalStateException("Constraints must be <= or >= :" + line);
                            }
                            problem.addConstraint(currentCoefs, constraintValue, bound == '>');
                            // Prepare for the next line
                            currentCoefs = Maps.newHashMap();
                        }
                        break;
                    }
                }
            }
        }

        // Index all of the variable definitions.
        this.variablePositions = Lists.newArrayList();
        Map<String, Integer> variableIndices = Maps.newHashMap();
        int index = 0;
        for (String varName : problem.getVariables()) {
            variableIndices.put(varName, index++);
            variablePositions.add(varName);
        }

        // Finally done. Now to construct everything.
        this.objective = problem.getObjective(variableIndices);
        this.constraints = problem.getConstraints(variableIndices);
    }

    private static class LineState {
        public int lineNumber;
        public int lastSign;
        public boolean hasLimit;

        public LineState() {
            this.lineNumber = 1;
            this.lastSign = 1;
            this.hasLimit = false;
        }

        public LineState update(int lastSign, boolean hasLimit) {
            this.lastSign = lastSign;
            this.hasLimit = hasLimit;
            lineNumber++;
            return this;
        }
    }

    private static Expression create(Map<String, Integer> values, Map<String, Integer> variableIndices) {
        int[] coefs = new int[variableIndices.size()];
        for (Map.Entry<String, Integer> e : values.entrySet()) {
            coefs[variableIndices.get(e.getKey())] = e.getValue();
        }
        return Expression.of(coefs);
    }
}