From ae31e0faf999ce46fbd8c03f703e968b83de7ab8 Mon Sep 17 00:00:00 2001 From: Alex Birkett Date: Sat, 31 Jan 2015 13:12:57 +0100 Subject: [PATCH] Initial commit No idea if this works or not --- .gitignore | 2 + build.gradle | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 50508 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew.bat | 90 ++++ local.properties | 11 + settings.gradle | 4 + src/main/java/no/birkett/kiwi/Constraint.java | 53 +++ .../kiwi/DuplicateConstraintException.java | 11 + src/main/java/no/birkett/kiwi/Expression.java | 64 +++ .../no/birkett/kiwi/InternalSolverError.java | 11 + .../no/birkett/kiwi/RelationalOperator.java | 10 + .../kiwi/RequiredFailureException.java | 7 + src/main/java/no/birkett/kiwi/Row.java | 217 +++++++++ src/main/java/no/birkett/kiwi/Solver.java | 432 ++++++++++++++++++ src/main/java/no/birkett/kiwi/Strength.java | 32 ++ src/main/java/no/birkett/kiwi/Symbol.java | 52 +++ src/main/java/no/birkett/kiwi/Term.java | 39 ++ .../UnsatisfiableConstraintException.java | 10 + src/main/java/no/birkett/kiwi/Util.java | 12 + src/main/java/no/birkett/kiwi/Variable.java | 35 ++ src/test/java/no/birkett/kiwi/Tests.java | 173 +++++++ 22 files changed, 1288 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew.bat create mode 100644 local.properties create mode 100644 settings.gradle create mode 100644 src/main/java/no/birkett/kiwi/Constraint.java create mode 100644 src/main/java/no/birkett/kiwi/DuplicateConstraintException.java create mode 100644 src/main/java/no/birkett/kiwi/Expression.java create mode 100644 src/main/java/no/birkett/kiwi/InternalSolverError.java create mode 100644 src/main/java/no/birkett/kiwi/RelationalOperator.java create mode 100644 src/main/java/no/birkett/kiwi/RequiredFailureException.java create mode 100644 src/main/java/no/birkett/kiwi/Row.java create mode 100644 src/main/java/no/birkett/kiwi/Solver.java create mode 100644 src/main/java/no/birkett/kiwi/Strength.java create mode 100644 src/main/java/no/birkett/kiwi/Symbol.java create mode 100644 src/main/java/no/birkett/kiwi/Term.java create mode 100644 src/main/java/no/birkett/kiwi/UnsatisfiableConstraintException.java create mode 100644 src/main/java/no/birkett/kiwi/Util.java create mode 100644 src/main/java/no/birkett/kiwi/Variable.java create mode 100644 src/test/java/no/birkett/kiwi/Tests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fc6363 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +/build diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..170d1ac --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'java' + +sourceCompatibility = 1.5 +version = '1.0' + +task wrapper(type: Wrapper) { + gradleVersion = '1.9' + distributionUrl = 'http://services.gradle.org/distributions/gradle-1.9-all.zip' +} + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d5c591c9c532da774b062aa6d7ab16405ff8700a GIT binary patch literal 50508 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%0DD3;W?!&t=FUIvF}Y7;NYDq6$vu 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 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..0cb7df1 --- /dev/null +++ b/local.properties @@ -0,0 +1,11 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Sat Jan 31 12:43:25 CET 2015 +sdk.dir=/Users/alex/sdk diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..5c2fc38 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'kiwi-java' + +include 'lib' + diff --git a/src/main/java/no/birkett/kiwi/Constraint.java b/src/main/java/no/birkett/kiwi/Constraint.java new file mode 100644 index 0000000..10192fd --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Constraint.java @@ -0,0 +1,53 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class Constraint { + + private Expression expression; + private double strength; + private RelationalOperator op; + + public Constraint(){ + } + + public Constraint(Expression expr, RelationalOperator op) { + this(expr, op, Strength.REQUIRED); + } + + public Constraint(Expression expr, RelationalOperator op, double strength) { + this.expression = expr; + this.op = op; + this.strength = Strength.clip(strength); + } + + public Constraint(Constraint other, RelationalOperator op) { + this(other.expression, other.op, other.strength); + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public double getStrength() { + return strength; + } + + public void setStrength(double strength) { + this.strength = strength; + } + + public RelationalOperator getOp() { + return op; + } + + public void setOp(RelationalOperator op) { + this.op = op; + } + +} diff --git a/src/main/java/no/birkett/kiwi/DuplicateConstraintException.java b/src/main/java/no/birkett/kiwi/DuplicateConstraintException.java new file mode 100644 index 0000000..c5a8c37 --- /dev/null +++ b/src/main/java/no/birkett/kiwi/DuplicateConstraintException.java @@ -0,0 +1,11 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class DuplicateConstraintException extends Exception { + + public DuplicateConstraintException(Constraint constraint) { + + } +} diff --git a/src/main/java/no/birkett/kiwi/Expression.java b/src/main/java/no/birkett/kiwi/Expression.java new file mode 100644 index 0000000..ba095d6 --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Expression.java @@ -0,0 +1,64 @@ +package no.birkett.kiwi; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by alex on 30/01/15. + */ +public class Expression { + + private List terms; + + private double constant; + + public Expression() { + this(0); + } + + public Expression(double constant) { + this.constant = constant; + this.terms = new ArrayList(); + } + + public Expression(Term term, double constant) { + this.terms = new ArrayList(); + terms.add(term); + this.constant = constant; + } + + public Expression(Term term) { + this (term, 0.0); + } + + public Expression(List terms, double constant) { + this.terms = terms; + this.constant = constant; + } + + public double getConstant() { + return constant; + } + + public void setConstant(double constant) { + this.constant = constant; + } + + public List getTerms() { + return terms; + } + + public void setTerms(List terms) { + this.terms = terms; + } + + public double getValue() { + double result = this.constant; + + for (Term term : terms) { + result += term.getValue(); + } + return result; + } +} + diff --git a/src/main/java/no/birkett/kiwi/InternalSolverError.java b/src/main/java/no/birkett/kiwi/InternalSolverError.java new file mode 100644 index 0000000..61694ab --- /dev/null +++ b/src/main/java/no/birkett/kiwi/InternalSolverError.java @@ -0,0 +1,11 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 31/01/15. + */ +public class InternalSolverError extends RuntimeException { + + public InternalSolverError(String string) { + + } +} diff --git a/src/main/java/no/birkett/kiwi/RelationalOperator.java b/src/main/java/no/birkett/kiwi/RelationalOperator.java new file mode 100644 index 0000000..b87720a --- /dev/null +++ b/src/main/java/no/birkett/kiwi/RelationalOperator.java @@ -0,0 +1,10 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 31/01/15. + */ +public enum RelationalOperator { + OP_LE, + OP_GE, + OP_EQ +} diff --git a/src/main/java/no/birkett/kiwi/RequiredFailureException.java b/src/main/java/no/birkett/kiwi/RequiredFailureException.java new file mode 100644 index 0000000..dc5524b --- /dev/null +++ b/src/main/java/no/birkett/kiwi/RequiredFailureException.java @@ -0,0 +1,7 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class RequiredFailureException extends Exception { +} diff --git a/src/main/java/no/birkett/kiwi/Row.java b/src/main/java/no/birkett/kiwi/Row.java new file mode 100644 index 0000000..588bcd3 --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Row.java @@ -0,0 +1,217 @@ +package no.birkett.kiwi; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Created by alex on 30/01/15. + */ +public class Row { + + private double constant; + + private Map cells = new HashMap(); + + public Row() { + this(0); + } + + public Row(double constant) { + this.constant = constant; + } + + public Row(Row other) { + this.cells = other.cells; + this.constant = other.constant; + } + + public double getConstant() { + return constant; + } + + public void setConstant(double constant) { + this.constant = constant; + } + + public Map getCells() { + return cells; + } + + public void setCells(Map cells) { + this.cells = cells; + } + + /** + * Add a constant value to the row constant. + * + * @return The new value of the constant + */ + double add(double value) { + return this.constant += value; + } + + /** + * Insert a symbol into the row with a given coefficient. + *

+ * If the symbol already exists in the row, the coefficient will be + * added to the existing coefficient. If the resulting coefficient + * is zero, the symbol will be removed from the row + */ + void insert(Symbol symbol, double coefficient) { + + Double existingCoefficient = cells.get(symbol); + + if (existingCoefficient != null) { + coefficient = existingCoefficient; + } + + if (Util.nearZero(coefficient)) { + cells.remove(symbol); + } else { + cells.put(symbol, Double.valueOf(coefficient)); + } + } + + /** + * Insert a symbol into the row with a given coefficient. + *

+ * If the symbol already exists in the row, the coefficient will be + * added to the existing coefficient. If the resulting coefficient + * is zero, the symbol will be removed from the row + */ + void insert(Symbol symbol) { + insert(symbol, 1.0); + } + + /** + * Insert a row into this row with a given coefficient. + * The constant and the cells of the other row will be multiplied by + * the coefficient and added to this row. Any cell with a resulting + * coefficient of zero will be removed from the row. + * + * @param other + * @param coefficient + */ + void insert(Row other, double coefficient) { + this.constant += other.constant * coefficient; + + Set> map = other.getCells().entrySet(); + + for (Map.Entry entry : map) { + double coeff = entry.getValue() * coefficient; + insert(entry.getKey(), coeff); + } + } + + /** + * Insert a row into this row with a given coefficient. + * The constant and the cells of the other row will be multiplied by + * the coefficient and added to this row. Any cell with a resulting + * coefficient of zero will be removed from the row. + * + * @param other + */ + void insert(Row other) { + insert(other, 0); + } + + /** + * Remove the given symbol from the row. + */ + void remove(Symbol symbol) { + + cells.remove(symbol); + // not sure what this does, can the symbol be added more than once? + /*CellMap::iterator it = m_cells.find( symbol ); + if( it != m_cells.end() ) + m_cells.erase( it );*/ + } + + /** + * Reverse the sign of the constant and all cells in the row. + */ + void reverseSign() { + this.constant = -this.constant; + + Set> map = getCells().entrySet(); + + for (Map.Entry entry : map) { + entry.setValue(-entry.getValue()); + } + } + + /** + * Solve the row for the given symbol. + *

+ * This method assumes the row is of the form a * x + b * y + c = 0 + * and (assuming solve for x) will modify the row to represent the + * right hand side of x = -b/a * y - c / a. The target symbol will + * be removed from the row, and the constant and other cells will + * be multiplied by the negative inverse of the target coefficient. + * The given symbol *must* exist in the row. + * + * @param symbol + */ + void solveFor(Symbol symbol) { + double coeff = -1.0 / cells.get(symbol); + cells.remove(symbol); + this.constant *= coeff; + + Set> map = getCells().entrySet(); + + for (Map.Entry entry : map) { + entry.setValue(entry.getValue() * coeff); + } + } + + /** + * Solve the row for the given symbols. + *

+ * This method assumes the row is of the form x = b * y + c and will + * solve the row such that y = x / b - c / b. The rhs symbol will be + * removed from the row, the lhs added, and the result divided by the + * negative inverse of the rhs coefficient. + * The lhs symbol *must not* exist in the row, and the rhs symbol + * must* exist in the row. + * + * @param lhs + * @param rhs + */ + void solveFor(Symbol lhs, Symbol rhs) { + insert(lhs, -1.0); + solveFor(rhs); + } + + /** + * Get the coefficient for the given symbol. + *

+ * If the symbol does not exist in the row, zero will be returned. + * + * @return + */ + double coefficientFor(Symbol symbol) { + if (this.cells.containsKey(symbol)) { + return this.cells.get(symbol); + } else { + return 0.0; + } + } + + /** + * Substitute a symbol with the data from another row. + *

+ * Given a row of the form a * x + b and a substitution of the + * form x = 3 * y + c the row will be updated to reflect the + * expression 3 * a * y + a * c + b. + * If the symbol does not exist in the row, this is a no-op. + */ + void substitute(Symbol symbol, Row row) { + if (cells.containsKey(symbol)) { + double coefficient = cells.get(symbol); + cells.remove(symbol); + insert(row, coefficient); + } + } + +} diff --git a/src/main/java/no/birkett/kiwi/Solver.java b/src/main/java/no/birkett/kiwi/Solver.java new file mode 100644 index 0000000..c150020 --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Solver.java @@ -0,0 +1,432 @@ +package no.birkett.kiwi; + + +import java.util.*; + +/** + * Created by alex on 30/01/15. + */ +public class Solver { + + private static class Tag { + Symbol marker; + Symbol other; + } + + private static class EditInfo { + Tag tag; + Constraint constraint; + double constant; + } + + private static class RowAndTag { + Tag tag; + Row row; + } + + private Map cns = new HashMap(); + private Map rows = new HashMap(); + private Map vars = new HashMap(); + private Map edits = new HashMap(); + private List infeasibleRows = new ArrayList(); + private Row objective = new Row(); + private Row artificial; + private long idTick = 1; + + + /** + * Add a constraint to the solver. + * + * @param constraint + * @throws DuplicateConstraintException The given constraint has already been added to the solver. + * @throws UnsatisfiableConstraintException The given constraint is required and cannot be satisfied. + */ + public void addConstraint(Constraint constraint) throws DuplicateConstraintException, UnsatisfiableConstraintException { + + if (cns.containsKey(constraint)) { + throw new DuplicateConstraintException(constraint); + } + + // Creating a row causes symbols to reserved for the variables + // in the constraint. If this method exits with an exception, + // then its possible those variables will linger in the var map. + // Since its likely that those variables will be used in other + // constraints and since exceptional conditions are uncommon, + // i'm not too worried about aggressive cleanup of the var map. + + RowAndTag rowAndTag = createRow(constraint); + + Symbol subject = chooseSubject(rowAndTag.row, rowAndTag.tag); + + // If chooseSubject could find a valid entering symbol, one + // last option is available if the entire row is composed of + // dummy variables. If the constant of the row is zero, then + // this represents redundant constraints and the new dummy + // marker can enter the basis. If the constant is non-zero, + // then it represents an unsatisfiable constraint. + if (subject.getType() == Symbol.Type.INVALID && allDummies(rowAndTag.row)) { + if (!Util.nearZero(rowAndTag.row.getConstant())) { + throw new UnsatisfiableConstraintException(constraint); + } else { + subject = rowAndTag.tag.marker; + } + } + + // If an entering symbol still isn't found, then the row must + // be added using an artificial variable. If that fails, then + // the row represents an unsatisfiable constraint. + if (subject.getType() == Symbol.Type.INVALID) { + if (!addWithArtificialVariable(rowAndTag.row)) { + throw new UnsatisfiableConstraintException(constraint); + } + } else { + rowAndTag.row.solveFor(subject); + substitute(subject, rowAndTag.row); + this.rows.put(subject, rowAndTag.row); + } + + this.cns.put(constraint, rowAndTag.tag); + + // Optimizing after each constraint is added performs less + // aggregate work due to a smaller average system size. It + // also ensures the solver remains in a consistent state. + optimize(this.objective); + } + + /** + * Update the values of the external solver variables. + */ + void updateVariables() { + + for (Map.Entry varEntry : vars.entrySet()) { + Variable variable = varEntry.getKey(); + Row row = this.rows.get(varEntry.getValue()); + + if (row == null) { + variable.setValue(0); + } else { + variable.setValue(row.getConstant()); + } + } + } + + + /** + * Create a new Row object for the given constraint. + *

+ * The terms in the constraint will be converted to cells in the row. + * Any term in the constraint with a coefficient of zero is ignored. + * This method uses the `getVarSymbol` method to get the symbol for + * the variables added to the row. If the symbol for a given cell + * variable is basic, the cell variable will be substituted with the + * basic row. + *

+ * The necessary slack and error variables will be added to the row. + * If the constant for the row is negative, the sign for the row + * will be inverted so the constant becomes positive. + *

+ * The tag will be updated with the marker and error symbols to use + * for tracking the movement of the constraint in the tableau. + */ + RowAndTag createRow(Constraint constraint) { + + Expression expr = constraint.getExpression(); + Row row = new Row(expr.getConstant()); + Tag tag = new Tag(); + + // Substitute the current basic variables into the row. + for (Term term : expr.getTerms()) { + if (!Util.nearZero(term.getCoefficient())) { + Symbol symbol = getVarSymbol(term.getVariable()); + + Row otherRow = rows.get(symbol); + + if (otherRow == null) { + row.insert(symbol, term.getCoefficient()); + } else { + row.insert(otherRow, term.getCoefficient()); + } + } + } + + // Add the necessary slack, error, and dummy variables. + switch (constraint.getOp()) { + case OP_LE: + case OP_GE: { + double coeff = constraint.getOp() == RelationalOperator.OP_LE ? 1.0 : -1.0; + Symbol slack = new Symbol(Symbol.Type.SLACK, idTick++); + tag.marker = slack; + row.insert(slack, coeff); + if (constraint.getStrength() < Strength.REQUIRED) { + Symbol error = new Symbol(Symbol.Type.ERROR, idTick++); + tag.other = error; + row.insert(error, -coeff); + this.objective.insert(error, constraint.getStrength()); + } + break; + } + case OP_EQ: { + if (constraint.getStrength() < Strength.REQUIRED) { + Symbol errplus = new Symbol(Symbol.Type.ERROR, idTick++); + Symbol errminus = new Symbol(Symbol.Type.ERROR, idTick++); + tag.marker = errplus; + tag.other = errminus; + row.insert(errplus, -1.0); // v = eplus - eminus + row.insert(errminus, 1.0); // v - eplus + eminus = 0 + this.objective.insert(errplus, constraint.getStrength()); + this.objective.insert(errminus, constraint.getStrength()); + } else { + Symbol dummy = new Symbol(Symbol.Type.DUMMY, idTick++); + tag.marker = dummy; + row.insert(dummy); + } + break; + } + } + + // Ensure the row as a positive constant. + if (row.getConstant() < 0.0) { + row.reverseSign(); + } + + RowAndTag rowAndTag = new RowAndTag(); + rowAndTag.row = row; + rowAndTag.tag = tag; + + + return rowAndTag; + } + + /** + * Choose the subject for solving for the row + *

+ * This method will choose the best subject for using as the solve + * target for the row. An invalid symbol will be returned if there + * is no valid target. + * The symbols are chosen according to the following precedence: + * 1) The first symbol representing an external variable. + * 2) A negative slack or error tag variable. + * If a subject cannot be found, an invalid symbol will be returned. + */ + private static Symbol chooseSubject(Row row, Tag tag) { + + for (Map.Entry cell : row.getCells().entrySet()) { + if (cell.getKey().getType() == Symbol.Type.EXTERNAL) { + return cell.getKey(); + } + } + if (tag.marker.getType() == Symbol.Type.SLACK || tag.marker.getType() == Symbol.Type.ERROR) { + if (row.coefficientFor(tag.marker) < 0.0) + return tag.marker; + } + if (tag.other.getType() == Symbol.Type.SLACK || tag.other.getType() == Symbol.Type.ERROR) { + if (row.coefficientFor(tag.other) < 0.0) + return tag.other; + } + return new Symbol(); + } + + /** + * Add the row to the tableau using an artificial variable. + *

+ * This will return false if the constraint cannot be satisfied. + */ + private boolean addWithArtificialVariable(Row row) { + //TODO check this + + // Create and add the artificial variable to the tableau + + Symbol art = new Symbol(Symbol.Type.SLACK, idTick++); + rows.put(art, new Row(row)); + + this.artificial = new Row(row); + + // Optimize the artificial objective. This is successful + // only if the artificial objective is optimized to zero. + optimize(this.artificial); + boolean success = Util.nearZero(artificial.getConstant()); + artificial = null; + + // If the artificial variable is basic, pivot the row so that + // it becomes basic. If the row is constant, exit early. + + Row rowptr = this.rows.get(art); + + if (rowptr != null) { + rows.remove(rowptr); + if (rowptr.getCells().isEmpty()) { + return success; + } + Symbol entering = anyPivotableSymbol(rowptr); + if (entering.getType() == Symbol.Type.INVALID) { + return false; // unsatisfiable (will this ever happen?) + } + rowptr.solveFor(art, entering); + substitute(entering, rowptr); + this.rows.put(entering, rowptr); + } + + // Remove the artificial variable from the tableau. + for (Map.Entry rowEntry : rows.entrySet()) { + rowEntry.getValue().remove(art); + } + + objective.remove(art); + + return success; + } + + /** + * Substitute the parametric symbol with the given row. + *

+ * This method will substitute all instances of the parametric symbol + * in the tableau and the objective function with the given row. + */ + void substitute(Symbol symbol, Row row) { + for (Map.Entry rowEntry : rows.entrySet()) { + rowEntry.getValue().substitute(symbol, row); + if (rowEntry.getKey().getType() != Symbol.Type.EXTERNAL && rowEntry.getValue().getConstant() < 0.0) { + infeasibleRows.add(rowEntry.getKey()); + } + } + + objective.substitute(symbol, row); + + if (artificial != null) { + artificial.substitute(symbol, row); + } + } + + /** + * Optimize the system for the given objective function. + *

+ * This method performs iterations of Phase 2 of the simplex method + * until the objective function reaches a minimum. + * + * @throws InternalSolverError The value of the objective function is unbounded. + */ + void optimize(Row objective) { + while (true) { + Symbol entering = getEnteringSymbol(objective); + if (entering.getType() == Symbol.Type.INVALID) { + return; + } + + Map.Entry entry = getLeavingRow(entering); + + if (entry == null) { + throw new InternalSolverError("The objective is unbounded."); + } + + // pivot the entering symbol into the basis + Symbol leaving = entry.getKey(); + Row row = entry.getValue(); + + this.rows.remove(entry.getKey()); + + row.solveFor(leaving, entering); + + substitute(entering, row); + + this.rows.put(entering, row); + } + } + + + /** + * Compute the entering variable for a pivot operation. + *

+ * This method will return first symbol in the objective function which + * is non-dummy and has a coefficient less than zero. If no symbol meets + * the criteria, it means the objective function is at a minimum, and an + * invalid symbol is returned. + */ + private static Symbol getEnteringSymbol(Row objective) { + + for (Map.Entry cell : objective.getCells().entrySet()) { + + if (cell.getKey().getType() != Symbol.Type.DUMMY && cell.getValue() < 0.0) { + return cell.getKey(); + } + } + return new Symbol(); + + } + + /** + * Get the first Slack or Error symbol in the row. + *

+ * If no such symbol is present, and Invalid symbol will be returned. + */ + private Symbol anyPivotableSymbol(Row row) { + Symbol symbol = null; + for (Map.Entry entry : objective.getCells().entrySet()) { + if (entry.getKey().getType() == Symbol.Type.SLACK || entry.getKey().getType() == Symbol.Type.ERROR) { + symbol = entry.getKey(); + } + } + if (symbol == null) { + symbol = new Symbol(); + } + return symbol; + } + + /** + * Compute the row which holds the exit symbol for a pivot. + *

+ * This documentation is copied from the C++ version and is outdated + *

+ *

+ * This method will return an iterator to the row in the row map + * which holds the exit symbol. If no appropriate exit symbol is + * found, the end() iterator will be returned. This indicates that + * the objective function is unbounded. + */ + private Map.Entry getLeavingRow(Symbol entering) { + // TODO check + double ratio = Double.MAX_VALUE; + Map.Entry found = null; + for (Map.Entry row : rows.entrySet()) { + if (row.getKey().getType() != Symbol.Type.EXTERNAL) { + double temp = row.getValue().coefficientFor(entering); + if (temp < 0.0) { + double temp_ratio = -row.getValue().getConstant() / temp; + if (temp_ratio < ratio) { + ratio = temp_ratio; + found = row; + } + } + } + } + return found; + } + + /** + * Get the symbol for the given variable. + *

+ * If a symbol does not exist for the variable, one will be created. + */ + private Symbol getVarSymbol(Variable variable) { + Symbol symbol; + if (vars.containsKey(variable)) { + symbol = vars.get(variable); + } else { + symbol = new Symbol(Symbol.Type.EXTERNAL, idTick++); + vars.put(variable, symbol); + } + return symbol; + } + + /** + * Test whether a row is composed of all dummy variables. + */ + private static boolean allDummies(Row row) { + for (Map.Entry cell : row.getCells().entrySet()) { + if (cell.getKey().getType() != Symbol.Type.DUMMY) { + return false; + } + } + return true; + } + +} diff --git a/src/main/java/no/birkett/kiwi/Strength.java b/src/main/java/no/birkett/kiwi/Strength.java new file mode 100644 index 0000000..f04ff0f --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Strength.java @@ -0,0 +1,32 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class Strength { + + public static final double REQUIRED = create(1000.0, 1000.0, 1000.0); + + public static final double STRONG = create(1.0, 0.0, 0.0); + + public static final double MEDIUM = create(0.0, 1.0, 0.0); + + public static final double WEAK = create(0.0, 0.0, 1.0); + + + public static final double create(double a, double b, double c, double w) { + double result = 0.0; + result += Math.max(0.0, Math.min(1000.0, a * w)) * 1000000.0; + result += Math.max(0.0, Math.min(1000.0, b * w)) * 1000.0; + result += Math.max(0.0, Math.min(1000.0, c * w)); + return result; + } + + public static final double create(double a, double b, double c) { + return create(a, b, c, 1.0); + } + + public static final double clip(double value) { + return Math.max(0.0, Math.min(REQUIRED, value)); + } +} diff --git a/src/main/java/no/birkett/kiwi/Symbol.java b/src/main/java/no/birkett/kiwi/Symbol.java new file mode 100644 index 0000000..6347a9b --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Symbol.java @@ -0,0 +1,52 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class Symbol { + + enum Type { + INVALID, + EXTERNAL, + SLACK, + ERROR, + DUMMY + } + + private Type type; + private long id; + + public Symbol() { + this(Type.INVALID, 0); + } + + public Symbol(Type type, long id) { + this.type = type; + this.id = id; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + boolean lessThan(Symbol other) { + return this.id < other.getId(); + } + + boolean equals(Symbol other) { + return this.id == other.getId(); + } + +} diff --git a/src/main/java/no/birkett/kiwi/Term.java b/src/main/java/no/birkett/kiwi/Term.java new file mode 100644 index 0000000..6516aa9 --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Term.java @@ -0,0 +1,39 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class Term { + + private Variable variable; + double coefficient; + + public Term(Variable variable, double coefficient) { + this.variable = variable; + this.coefficient = coefficient; + } + + public Term(Variable variable) { + this(variable, 1.0); + } + + public Variable getVariable() { + return variable; + } + + public void setVariable(Variable variable) { + this.variable = variable; + } + + public double getCoefficient() { + return coefficient; + } + + public void setCoefficient(double coefficient) { + this.coefficient = coefficient; + } + + public double getValue() { + return coefficient * variable.getValue(); + } +} diff --git a/src/main/java/no/birkett/kiwi/UnsatisfiableConstraintException.java b/src/main/java/no/birkett/kiwi/UnsatisfiableConstraintException.java new file mode 100644 index 0000000..4d36c7a --- /dev/null +++ b/src/main/java/no/birkett/kiwi/UnsatisfiableConstraintException.java @@ -0,0 +1,10 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class UnsatisfiableConstraintException extends Exception { + public UnsatisfiableConstraintException(Constraint constraint) { + + } +} diff --git a/src/main/java/no/birkett/kiwi/Util.java b/src/main/java/no/birkett/kiwi/Util.java new file mode 100644 index 0000000..c16f771 --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Util.java @@ -0,0 +1,12 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class Util { + private static double EPS = 1.0e-8; + + public static boolean nearZero(double value ) { + return value < 0.0 ? -value < EPS : value < EPS; + } +} diff --git a/src/main/java/no/birkett/kiwi/Variable.java b/src/main/java/no/birkett/kiwi/Variable.java new file mode 100644 index 0000000..68dc903 --- /dev/null +++ b/src/main/java/no/birkett/kiwi/Variable.java @@ -0,0 +1,35 @@ +package no.birkett.kiwi; + +/** + * Created by alex on 30/01/15. + */ +public class Variable { + + private String name; + + private double value; + + public Variable(String name) { + this.name = name; + } + + public Variable(double value) { + + } + + public double getValue() { + return value; + } + + public void setValue(double value) { + this.value = value; + } + + public Expression times(double value) { + return null; + } + + public Expression plus(double value) { + return null; + } +} diff --git a/src/test/java/no/birkett/kiwi/Tests.java b/src/test/java/no/birkett/kiwi/Tests.java new file mode 100644 index 0000000..27443e4 --- /dev/null +++ b/src/test/java/no/birkett/kiwi/Tests.java @@ -0,0 +1,173 @@ +package no.birkett.kiwi; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + + +public class Tests { + + private static double EPSILON = 1.0e-8; + + @Test + public void testKiwi() throws UnsatisfiableConstraintException, DuplicateConstraintException { + Solver solver = new Solver(); + Variable x = new Variable("x"); + Variable y = new Variable("y"); + + + Term term = new Term(x); + + Expression expression = new Expression(term); + + Constraint constraint = new Constraint(expression, RelationalOperator.OP_EQ); + + solver.addConstraint(constraint); + solver.updateVariables(); + } + + /*@Test + public void simple1() { + Variable x = new Variable(167); + Variable y = new Variable(2); + Solver solver = new Solver(); + + Constraint eq = new Constraint(x, Constraint.Operator.EQ, new Expression(y)); + //ClLinearEquation eq = new ClLinearEquation(x, new ClLinearExpression(y)); + solver.addConstraint(eq); + assertEquals(x.value(), y.value(), EPSILON); + } + + + @Test + public void addDelete1() { + Variable x = new Variable("x"); + Solver solver = new Solver(); + + solver.addConstraint(new Constraint(x, Constraint.Operator.EQ, 100, Strength.WEAK)); + + Constraint c10 = new Constraint(x, Constraint.Operator.LEQ, 10.0); + Constraint c20 = new Constraint(x, Constraint.Operator.LEQ, 20.0); + + solver.addConstraint(c10); + solver.addConstraint(c20); + + assertEquals(10, x.value(), EPSILON); + + solver.removeConstraint(c10); + assertEquals(20, x.value(), EPSILON); + + solver.removeConstraint(c20); + assertEquals(100, x.value(), EPSILON); + + Constraint c10again = new Constraint(x, Constraint.Operator.LEQ, 10.0); + + solver.addConstraint(c10); + solver.addConstraint(c10again); + + assertEquals(10, x.value(), EPSILON); + + solver.removeConstraint(c10); + assertEquals(10, x.value(), EPSILON); + + solver.removeConstraint(c10again); + assertEquals(100, x.value(), EPSILON); + } + + + @Test + public void addDelete2() { + Variable x = new Variable("x"); + Variable y = new Variable("y"); + Solver solver = new Solver(); + + solver.addConstraint(new Constraint(x, Constraint.Operator.EQ, 100.0, Strength.WEAK)); + solver.addConstraint(new Constraint(y, Constraint.Operator.EQ, 120.0, Strength.STRONG)); + + + Constraint c10 = new Constraint(x, Constraint.Operator.LEQ, 10.0); + Constraint c20 = new Constraint(x, Constraint.Operator.LEQ, 20.0); + + solver.addConstraint(c10); + solver.addConstraint(c20); + + assertEquals(10, x.value(), EPSILON); + assertEquals(120, y.value(), EPSILON); + + solver.removeConstraint(c10); + assertEquals(20, x.value(), EPSILON); + assertEquals(120, y.value(), EPSILON); + + Constraint cxy = new Constraint(x.times(2.0), Constraint.Operator.EQ, y); + solver.addConstraint(cxy); + assertEquals(20, x.value(), EPSILON); + assertEquals(40, y.value(), EPSILON); + + solver.removeConstraint(c20); + assertEquals(60, x.value(), EPSILON); + assertEquals(120, y.value(), EPSILON); + + solver.removeConstraint(cxy); + assertEquals(100, x.value(), EPSILON); + assertEquals(120, y.value(), EPSILON); + } + + @Test + public void casso1() { + Variable x = new Variable("x"); + Variable y = new Variable("y"); + Solver solver = new Solver(); + + solver.addConstraint(new Constraint(x, Constraint.Operator.LEQ, y)); + solver.addConstraint(new Constraint(y, Constraint.Operator.EQ, x.plus(3.0))); + solver.addConstraint(new Constraint(x, Constraint.Operator.EQ, 10.0, Strength.WEAK)); + solver.addConstraint(new Constraint(y, Constraint.Operator.EQ, 10.0, Strength.WEAK)); + + if (Math.abs(x.getValue() - 10.0) < EPSILON) { + assertEquals(10, x.value(), EPSILON); + assertEquals(13, y.value(), EPSILON); + } else { + assertEquals(7, x.value(), EPSILON); + assertEquals(10, y.value(), EPSILON); + } + } + + @Test(expected = RequiredFailure.class) + public void inconsistent1() throws InternalError { + Variable x = new Variable("x"); + Solver solver = new Solver(); + + solver.addConstraint(new Constraint(x, Constraint.Operator.EQ, 10.0)); + solver.addConstraint(new Constraint(x, Constraint.Operator.EQ, 5.0)); + } + + + @Test(expected = RequiredFailure.class) + public void inconsistent2() { + Variable x = new Variable("x"); + Solver solver = new Solver(); + + solver.addConstraint(new Constraint(x, Constraint.Operator.GEQ, 10.0)); + solver.addConstraint(new Constraint(x, Constraint.Operator.LEQ, 5.0)); + } + + + + @Test(expected = RequiredFailure.class) + public void inconsistent3() { + + Variable w = new Variable("w"); + Variable x = new Variable("x"); + Variable y = new Variable("y"); + Variable z = new Variable("z"); + Solver solver = new Solver(); + + solver.addConstraint(new Constraint(w, Constraint.Operator.GEQ, 10.0)); + solver.addConstraint(new Constraint(x, Constraint.Operator.GEQ, w)); + solver.addConstraint(new Constraint(y, Constraint.Operator.GEQ, x)); + solver.addConstraint(new Constraint(z, Constraint.Operator.GEQ, y)); + solver.addConstraint(new Constraint(z, Constraint.Operator.GEQ, 8.0)); + solver.addConstraint(new Constraint(z, Constraint.Operator.LEQ, 4.0)); + }*/ + +}