Skip to content

Commit

Permalink
PLANNER-405 Big refactor: Score.getInitScore() to remove separate tra…
Browse files Browse the repository at this point in the history
…cking of uninitialized variable count: optaplanner-core impact only
  • Loading branch information
ge0ffrey committed May 31, 2016
1 parent 760f219 commit 2946d66
Show file tree
Hide file tree
Showing 139 changed files with 2,962 additions and 2,040 deletions.
Expand Up @@ -26,6 +26,55 @@
*/ */
public abstract class AbstractBendableScore<S extends FeasibilityScore<S>> extends AbstractScore<S> { public abstract class AbstractBendableScore<S extends FeasibilityScore<S>> extends AbstractScore<S> {


protected static final String HARD_LABEL = "hard";
protected static final String SOFT_LABEL = "soft";
protected static final String[] LEVEL_SUFFIXES = new String[]{HARD_LABEL, SOFT_LABEL};

protected static String[][] parseBendableScoreTokens(Class<? extends Score> scoreClass, String scoreString) {
String[][] scoreTokens = new String[3][];
scoreTokens[0] = new String[1];
int startIndex = 0;
int initEndIndex = scoreString.indexOf(INIT_LABEL, startIndex);
if (initEndIndex >= 0) {
scoreTokens[0][0] = scoreString.substring(startIndex, initEndIndex);
startIndex = initEndIndex + INIT_LABEL.length() + "/".length();
} else {
scoreTokens[0][0] = "0";
}
for (int i = 0; i < LEVEL_SUFFIXES.length; i++) {
String levelSuffix = LEVEL_SUFFIXES[i];
int endIndex = scoreString.indexOf(levelSuffix, startIndex);
if (endIndex < 0) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(true, LEVEL_SUFFIXES) + "):"
+ " the levelSuffix (" + levelSuffix
+ ") isn't in the scoreSubstring (" + scoreString.substring(startIndex) + ").");
}
String scoreSubString = scoreString.substring(startIndex, endIndex);
if (!scoreSubString.startsWith("[") || !scoreSubString.endsWith("]")) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(true, LEVEL_SUFFIXES) + "):"
+ " the scoreSubString (" + scoreSubString
+ ") does not start and end with \"[\" and \"]\".");
}
if (scoreSubString.equals("[]")) {
scoreTokens[1 + i] = new String[0];
} else {
scoreTokens[1 + i] = scoreSubString.substring(1, scoreSubString.length() - 1).split("/");
}
startIndex = endIndex + levelSuffix.length() + "/".length();
}
if (startIndex != scoreString.length() + "/".length()) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(true, LEVEL_SUFFIXES) + "):"
+ " the suffix (" + scoreString.substring(startIndex) + ") is unsupported.");
}
return scoreTokens;
}

public abstract int getHardLevelsSize(); public abstract int getHardLevelsSize();


public abstract int getSoftLevelsSize(); public abstract int getSoftLevelsSize();
Expand All @@ -35,4 +84,8 @@ public abstract class AbstractBendableScore<S extends FeasibilityScore<S>> exten
*/ */
public abstract int getLevelsSize(); public abstract int getLevelsSize();


protected AbstractBendableScore(int initScore) {
super(initScore);
}

} }
Expand Up @@ -19,6 +19,7 @@
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;


import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;


/** /**
Expand All @@ -30,64 +31,59 @@
*/ */
public abstract class AbstractScore<S extends Score> implements Score<S>, Serializable { public abstract class AbstractScore<S extends Score> implements Score<S>, Serializable {


protected static final String HARD_LABEL = "hard"; protected static final String INIT_LABEL = "init";
protected static final String SOFT_LABEL = "soft";


protected static String[] parseLevelStrings(Class<? extends Score> scoreClass, protected static String[] parseScoreTokens(Class<? extends Score> scoreClass,
String scoreString, String... levelSuffixes) { String scoreString, String... levelSuffixes) {
String[] scoreTokens = scoreString.split("/"); String[] scoreTokens = new String[levelSuffixes.length + 1];
if (scoreTokens.length != levelSuffixes.length) { String[] suffixedScoreTokens = scoreString.split("/");
int startIndex;
if (suffixedScoreTokens.length == levelSuffixes.length + 1) {
String suffixedScoreToken = suffixedScoreTokens[0];
if (!suffixedScoreToken.endsWith(INIT_LABEL)) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):"
+ " the suffixedScoreToken (" + suffixedScoreToken
+ ") does not end with levelSuffix (" + INIT_LABEL + ").");
}
scoreTokens[0] = suffixedScoreToken.substring(0, suffixedScoreToken.length() - INIT_LABEL.length());
startIndex = 1;
} else if (suffixedScoreTokens.length == levelSuffixes.length) {
scoreTokens[0] = "0";
startIndex = 0;
} else {
throw new IllegalArgumentException("The scoreString (" + scoreString throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName() + ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):" + ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):"
+ " the scoreTokens length (" + scoreTokens.length + " the suffixedScoreTokens length (" + suffixedScoreTokens.length
+ ") differs from the levelSuffixes length (" + levelSuffixes.length + ")."); + ") differs from the levelSuffixes length ("
+ levelSuffixes.length + " or " + (levelSuffixes.length + 1) + ").");
} }
String[] levelStrings = new String[levelSuffixes.length];
for (int i = 0; i < levelSuffixes.length; i++) { for (int i = 0; i < levelSuffixes.length; i++) {
if (!scoreTokens[i].endsWith(levelSuffixes[i])) { String suffixedScoreToken = suffixedScoreTokens[startIndex + i];
String levelSuffix = levelSuffixes[i];
if (!suffixedScoreToken.endsWith(levelSuffix)) {
throw new IllegalArgumentException("The scoreString (" + scoreString throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName() + ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):" + ") doesn't follow the correct pattern (" + buildScorePattern(false, levelSuffixes) + "):"
+ " the scoreToken (" + scoreTokens[i] + " the suffixedScoreToken (" + suffixedScoreToken
+ ") does not end with levelSuffix (" + levelSuffixes[i] + ")."); + ") does not end with levelSuffix (" + levelSuffix + ").");
} }
levelStrings[i] = scoreTokens[i].substring(0, scoreTokens[i].length() - levelSuffixes[i].length()); scoreTokens[1 + i] = suffixedScoreToken.substring(0, suffixedScoreToken.length() - levelSuffix.length());
} }
return levelStrings; return scoreTokens;
} }


protected static String[][] parseBendableLevelStrings(Class<? extends Score> scoreClass, protected static int parseInitScore(Class<? extends Score> scoreClass,
String scoreString, String... levelSuffixes) { String scoreString, String initScoreString) {
String[][] levelStrings = new String[levelSuffixes.length][]; try {
int startIndex = 0; return Integer.parseInt(initScoreString);
for (int i = 0; i < levelSuffixes.length; i++) { } catch (NumberFormatException e) {
String levelSuffix = levelSuffixes[i]; throw new IllegalArgumentException("The scoreString (" + scoreString
int endIndex = scoreString.indexOf(levelSuffix, startIndex); + ") for the scoreClass (" + scoreClass.getSimpleName() + ") has a initScoreString ("
if (endIndex < 0) { + initScoreString + ") which is not a valid integer.", e);
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(true, levelSuffixes) + "):"
+ " the levelSuffix (" + levelSuffix
+ ") isn't in the scoreSubstring (" + scoreString.substring(startIndex) + ").");
}
String scoreSubString = scoreString.substring(startIndex, endIndex);
if (!scoreSubString.startsWith("[") || !scoreSubString.endsWith("]")) {
throw new IllegalArgumentException("The scoreString (" + scoreString
+ ") for the scoreClass (" + scoreClass.getSimpleName()
+ ") doesn't follow the correct pattern (" + buildScorePattern(true, levelSuffixes) + "):"
+ " the scoreSubString (" + scoreSubString
+ ") does not start and end with \"[\" and \"]\".");
}
if (scoreSubString.equals("[]")) {
levelStrings[i] = new String[0];
} else {
String[] scoreTokens = scoreSubString.substring(1, scoreSubString.length() - 1).split("/");
levelStrings[i] = scoreTokens;
}
startIndex = endIndex + levelSuffix.length() + "/".length();
} }
return levelStrings;
} }


protected static int parseLevelAsInt(Class<? extends Score> scoreClass, protected static int parseLevelAsInt(Class<? extends Score> scoreClass,
Expand Down Expand Up @@ -153,9 +149,43 @@ protected static String buildScorePattern(boolean bendable, String... levelSuffi
return scorePattern.toString(); return scorePattern.toString();
} }


// ************************************************************************
// Fields
// ************************************************************************

protected final int initScore;

protected AbstractScore(int initScore) {
this.initScore = initScore;
if (initScore > 0) {
throw new IllegalArgumentException("The initScore (" + initScore + ") cannot be positive.");
}
}

@Override
public int getInitScore() {
return initScore;
}

// ************************************************************************
// Worker methods
// ************************************************************************

@Override
public boolean isSolutionInitialized() {
return initScore >= 0;
}

@Override @Override
public boolean isCompatibleArithmeticArgument(Score otherScore) { public boolean isCompatibleArithmeticArgument(Score otherScore) {
return getClass().isInstance(otherScore); return getClass().isInstance(otherScore);
} }


protected String getInitPrefix() {
if (initScore == 0) {
return "";
}
return initScore + INIT_LABEL + "/";
}

} }
Expand Up @@ -29,8 +29,9 @@
public interface FeasibilityScore<S extends FeasibilityScore> extends Score<S> { public interface FeasibilityScore<S extends FeasibilityScore> extends Score<S> {


/** /**
* A {@link PlanningSolution} is feasible if it has no broken hard constraints. * A {@link PlanningSolution} is feasible if it has no broken hard constraints
* @return true if the hard score is 0 or higher * and {@link #isSolutionInitialized()} is true.
* @return true if the hard score is 0 or higher and the {@link #getInitScore()} is 0.
*/ */
boolean isFeasible(); boolean isFeasible();


Expand Down
Expand Up @@ -16,6 +16,7 @@


package org.optaplanner.core.api.score; package org.optaplanner.core.api.score;


import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.impl.score.definition.ScoreDefinition; import org.optaplanner.core.impl.score.definition.ScoreDefinition;


Expand All @@ -33,6 +34,29 @@
*/ */
public interface Score<S extends Score> extends Comparable<S> { public interface Score<S extends Score> extends Comparable<S> {


/**
* The init score is the negative of the number of uninitialized genuine planning variables.
* If it's 0 (which is usually is), the {@link PlanningSolution} is fully initialized
* and the score's {@link #toString()} does not mention it.
* <p>
* During {@link #compareTo(Object)}, it's even more important than the hard score:
* if you don't want this behaviour, read about overconstrained planning in the reference manual.
* @return higher is better, always negative, 0 if all planning variables are initialized
*/
int getInitScore();

/**
* Checks if the {@link PlanningSolution} of this score was fully initialized when it was calculated.
* @return true if {@link #getInitScore()} is 0
*/
boolean isSolutionInitialized();

/**
* For example {@code -7init/0hard/-8soft} returns {@code 0hard/-8soft}.
* @return equal score except that {@link #getInitScore()} is {@code 0}.
*/
S toInitializedScore();

/** /**
* Returns a Score whose value is (this + augment). * Returns a Score whose value is (this + augment).
* @param augment value to be added to this Score * @param augment value to be added to this Score
Expand Down Expand Up @@ -83,7 +107,10 @@ public interface Score<S extends Score> extends Comparable<S> {
/** /**
* Returns a Score whose value is (- this). * Returns a Score whose value is (- this).
* @return - this * @return - this
* @deprecated Avoid usage, because it fails on a score with a non zero {@link #getInitScore()}
* https://issues.jboss.org/browse/PLANNER-584
*/ */
@Deprecated
S negate(); S negate();


/** /**
Expand All @@ -94,8 +121,11 @@ public interface Score<S extends Score> extends Comparable<S> {
* The length of the returned array must be stable for a specific {@link Score} implementation. * The length of the returned array must be stable for a specific {@link Score} implementation.
* <p> * <p>
* For example: {@code -0hard/-7soft} returns {@code new int{-0, -7}} * For example: {@code -0hard/-7soft} returns {@code new int{-0, -7}}
* <p>
* The level numbers do not contain the {@link #getInitScore()}.
* For example: {@code -3init/-0hard/-7soft} also returns {@code new int{-0, -7}}
* @return never null * @return never null
* @see ScoreDefinition#fromLevelNumbers(Number[]) * @see ScoreDefinition#fromLevelNumbers(int, Number[])
*/ */
Number[] toLevelNumbers(); Number[] toLevelNumbers();


Expand Down

0 comments on commit 2946d66

Please sign in to comment.