Browse Source

Initial commit

No idea if this works or not
master
Alex Birkett 7 years ago
parent
commit
ae31e0faf9
  1. 2
      .gitignore
  2. 17
      build.gradle
  3. BIN
      gradle/wrapper/gradle-wrapper.jar
  4. 6
      gradle/wrapper/gradle-wrapper.properties
  5. 90
      gradlew.bat
  6. 11
      local.properties
  7. 4
      settings.gradle
  8. 53
      src/main/java/no/birkett/kiwi/Constraint.java
  9. 11
      src/main/java/no/birkett/kiwi/DuplicateConstraintException.java
  10. 64
      src/main/java/no/birkett/kiwi/Expression.java
  11. 11
      src/main/java/no/birkett/kiwi/InternalSolverError.java
  12. 10
      src/main/java/no/birkett/kiwi/RelationalOperator.java
  13. 7
      src/main/java/no/birkett/kiwi/RequiredFailureException.java
  14. 217
      src/main/java/no/birkett/kiwi/Row.java
  15. 432
      src/main/java/no/birkett/kiwi/Solver.java
  16. 32
      src/main/java/no/birkett/kiwi/Strength.java
  17. 52
      src/main/java/no/birkett/kiwi/Symbol.java
  18. 39
      src/main/java/no/birkett/kiwi/Term.java
  19. 10
      src/main/java/no/birkett/kiwi/UnsatisfiableConstraintException.java
  20. 12
      src/main/java/no/birkett/kiwi/Util.java
  21. 35
      src/main/java/no/birkett/kiwi/Variable.java
  22. 173
      src/test/java/no/birkett/kiwi/Tests.java

2
.gitignore

@ -0,0 +1,2 @@
.idea/
/build

17
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'
}

BIN
gradle/wrapper/gradle-wrapper.jar

Binary file not shown.

6
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

90
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

11
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

4
settings.gradle

@ -0,0 +1,4 @@
rootProject.name = 'kiwi-java'
include 'lib'

53
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;
}
}

11
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) {
}
}

64
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<Term> terms;
private double constant;
public Expression() {
this(0);
}
public Expression(double constant) {
this.constant = constant;
this.terms = new ArrayList<Term>();
}
public Expression(Term term, double constant) {
this.terms = new ArrayList<Term>();
terms.add(term);
this.constant = constant;
}
public Expression(Term term) {
this (term, 0.0);
}
public Expression(List<Term> terms, double constant) {
this.terms = terms;
this.constant = constant;
}
public double getConstant() {
return constant;
}
public void setConstant(double constant) {
this.constant = constant;
}
public List<Term> getTerms() {
return terms;
}
public void setTerms(List<Term> terms) {
this.terms = terms;
}
public double getValue() {
double result = this.constant;
for (Term term : terms) {
result += term.getValue();
}
return result;
}
}

11
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) {
}
}

10
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
}

7
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 {
}

217
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<Symbol, Double> cells = new HashMap<Symbol, Double>();
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<Symbol, Double> getCells() {
return cells;
}
public void setCells(Map<Symbol, Double> 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.
* <p/>
* 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.
* <p/>
* 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.Entry<Symbol, Double>> map = other.getCells().entrySet();
for (Map.Entry<Symbol, Double> 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.Entry<Symbol, Double>> map = getCells().entrySet();
for (Map.Entry<Symbol, Double> entry : map) {
entry.setValue(-entry.getValue());
}
}
/**
* Solve the row for the given symbol.
* <p/>
* 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.Entry<Symbol, Double>> map = getCells().entrySet();
for (Map.Entry<Symbol, Double> entry : map) {
entry.setValue(entry.getValue() * coeff);
}
}
/**
* Solve the row for the given symbols.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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);
}
}
}

432
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<Constraint, Tag> cns = new HashMap<Constraint, Tag>();
private Map<Symbol, Row> rows = new HashMap<Symbol, Row>();
private Map<Variable, Symbol> vars = new HashMap<Variable, Symbol>();
private Map<Variable, EditInfo> edits = new HashMap<Variable, EditInfo>();
private List<Symbol> infeasibleRows = new ArrayList<Symbol>();
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<Variable, Symbol> 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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
* <p/>
* 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<Symbol, Double> 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.
* <p/>
* 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<Symbol, Row> rowEntry : rows.entrySet()) {
rowEntry.getValue().remove(art);
}
objective.remove(art);
return success;
}
/**
* Substitute the parametric symbol with the given row.
* <p/>
* 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<Symbol, Row> 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.
* <p/>
* 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<Symbol, Row> 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.
* <p/>
* 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<Symbol, Double> 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.
* <p/>
* If no such symbol is present, and Invalid symbol will be returned.
*/
private Symbol anyPivotableSymbol(Row row) {
Symbol symbol = null;
for (Map.Entry<Symbol, Double> 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.
* <p/>
* This documentation is copied from the C++ version and is outdated
* <p/>
* <p/>
* 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<Symbol, Row> getLeavingRow(Symbol entering) {
// TODO check
double ratio = Double.MAX_VALUE;
Map.Entry<Symbol, Row> found = null;
for (Map.Entry<Symbol, Row> 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.
* <p/>
* 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<Symbol, Double> cell : row.getCells().entrySet()) {
if (cell.getKey().getType() != Symbol.Type.DUMMY) {
return false;
}
}
return true;
}
}

32
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));
}
}

52
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();
}
}

39
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();
}
}

10
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) {
}
}

12
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;
}
}

35
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;
}
}

173
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));
}*/
}
Loading…
Cancel
Save