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 0000000..d5c591c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9ff4091 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jan 30 18:41:56 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 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)); + }*/ + +}