/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.core.api.score.buildin.hardmediumsoft;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.score.AbstractScore;
import org.optaplanner.core.api.score.FeasibilityScore;
import org.optaplanner.core.api.score.Score;
/**
* This {@link Score} is based on 3 levels of int constraints: hard, medium and soft.
* Hard constraints have priority over medium constraints.
* Medium constraints have priority over soft constraints.
* Hard constraints determine feasibility.
* <p>
* This class is immutable.
* @see Score
*/
public final class HardMediumSoftScore extends AbstractScore<HardMediumSoftScore>
implements FeasibilityScore<HardMediumSoftScore> {
public static final HardMediumSoftScore ZERO = new HardMediumSoftScore(0, 0, 0, 0);
private static final String HARD_LABEL = "hard";
private static final String MEDIUM_LABEL = "medium";
private static final String SOFT_LABEL = "soft";
public static HardMediumSoftScore parseScore(String scoreString) {
String[] scoreTokens = parseScoreTokens(HardMediumSoftScore.class, scoreString,
HARD_LABEL, MEDIUM_LABEL, SOFT_LABEL);
int initScore = parseInitScore(HardMediumSoftScore.class, scoreString, scoreTokens[0]);
int hardScore = parseLevelAsInt(HardMediumSoftScore.class, scoreString, scoreTokens[1]);
int mediumScore = parseLevelAsInt(HardMediumSoftScore.class, scoreString, scoreTokens[2]);
int softScore = parseLevelAsInt(HardMediumSoftScore.class, scoreString, scoreTokens[3]);
return valueOfUninitialized(initScore, hardScore, mediumScore, softScore);
}
public static HardMediumSoftScore valueOfUninitialized(int initScore, int hardScore, int mediumScore, int softScore) {
return new HardMediumSoftScore(initScore, hardScore, mediumScore, softScore);
}
public static HardMediumSoftScore valueOf(int hardScore, int mediumScore, int softScore) {
return new HardMediumSoftScore(0, hardScore, mediumScore, softScore);
}
// ************************************************************************
// Fields
// ************************************************************************
private final int hardScore;
private final int mediumScore;
private final int softScore;
/**
* Private default constructor for default marshalling/unmarshalling of unknown frameworks that use reflection.
* Such integration is always inferior to the specialized integration modules, such as
* optaplanner-persistence-jpa, optaplanner-persistence-xstream, optaplanner-persistence-jaxb, ...
*/
@SuppressWarnings("unused")
private HardMediumSoftScore() {
super(Integer.MIN_VALUE);
hardScore = Integer.MIN_VALUE;
mediumScore = Integer.MIN_VALUE;
softScore = Integer.MIN_VALUE;
}
private HardMediumSoftScore(int initScore, int hardScore, int mediumScore, int softScore) {
super(initScore);
this.hardScore = hardScore;
this.mediumScore = mediumScore;
this.softScore = softScore;
}
/**
* The total of the broken negative hard constraints and fulfilled positive hard constraints.
* Their weight is included in the total.
* The hard score is usually a negative number because most use cases only have negative constraints.
* @return higher is better, usually negative, 0 if no hard constraints are broken/fulfilled
*/
public int getHardScore() {
return hardScore;
}
/**
* The total of the broken negative medium constraints and fulfilled positive medium constraints.
* Their weight is included in the total.
* The medium score is usually a negative number because most use cases only have negative constraints.
* <p>
* In a normal score comparison, the medium score is irrelevant if the 2 scores don't have the same hard score.
* @return higher is better, usually negative, 0 if no medium constraints are broken/fulfilled
*/
public int getMediumScore() {
return mediumScore;
}
/**
* The total of the broken negative soft constraints and fulfilled positive soft constraints.
* Their weight is included in the total.
* The soft score is usually a negative number because most use cases only have negative constraints.
* <p>
* In a normal score comparison, the soft score is irrelevant if the 2 scores don't have the same hard and medium score.
* @return higher is better, usually negative, 0 if no soft constraints are broken/fulfilled
*/
public int getSoftScore() {
return softScore;
}
// ************************************************************************
// Worker methods
// ************************************************************************
@Override
public HardMediumSoftScore toInitializedScore() {
return initScore == 0 ? this : new HardMediumSoftScore(0, hardScore, mediumScore, softScore);
}
@Override
public HardMediumSoftScore withInitScore(int newInitScore) {
assertNoInitScore();
return new HardMediumSoftScore(newInitScore, hardScore, mediumScore, softScore);
}
/**
* A {@link PlanningSolution} is feasible if it has no broken hard constraints.
* @return true if the {@link #getHardScore()} is 0 or higher
*/
@Override
public boolean isFeasible() {
return initScore >= 0 && hardScore >= 0;
}
@Override
public HardMediumSoftScore add(HardMediumSoftScore augment) {
return new HardMediumSoftScore(
initScore + augment.getInitScore(),
hardScore + augment.getHardScore(),
mediumScore + augment.getMediumScore(),
softScore + augment.getSoftScore());
}
@Override
public HardMediumSoftScore subtract(HardMediumSoftScore subtrahend) {
return new HardMediumSoftScore(
initScore - subtrahend.getInitScore(),
hardScore - subtrahend.getHardScore(),
mediumScore - subtrahend.getMediumScore(),
softScore - subtrahend.getSoftScore());
}
@Override
public HardMediumSoftScore multiply(double multiplicand) {
return new HardMediumSoftScore(
(int) Math.floor(initScore * multiplicand),
(int) Math.floor(hardScore * multiplicand),
(int) Math.floor(mediumScore * multiplicand),
(int) Math.floor(softScore * multiplicand));
}
@Override
public HardMediumSoftScore divide(double divisor) {
return new HardMediumSoftScore(
(int) Math.floor(initScore / divisor),
(int) Math.floor(hardScore / divisor),
(int) Math.floor(mediumScore / divisor),
(int) Math.floor(softScore / divisor));
}
@Override
public HardMediumSoftScore power(double exponent) {
return new HardMediumSoftScore(
(int) Math.floor(Math.pow(initScore, exponent)),
(int) Math.floor(Math.pow(hardScore, exponent)),
(int) Math.floor(Math.pow(mediumScore, exponent)),
(int) Math.floor(Math.pow(softScore, exponent)));
}
@Override
public HardMediumSoftScore negate() {
return new HardMediumSoftScore(-initScore, -hardScore, -mediumScore, -softScore);
}
@Override
public Number[] toLevelNumbers() {
return new Number[]{hardScore, mediumScore, softScore};
}
@Override
public boolean equals(Object o) {
// A direct implementation (instead of EqualsBuilder) to avoid dependencies
if (this == o) {
return true;
} else if (o instanceof HardMediumSoftScore) {
HardMediumSoftScore other = (HardMediumSoftScore) o;
return initScore == other.getInitScore()
&& hardScore == other.getHardScore()
&& mediumScore == other.getMediumScore()
&& softScore == other.getSoftScore();
} else {
return false;
}
}
@Override
public int hashCode() {
// A direct implementation (instead of HashCodeBuilder) to avoid dependencies
return ((((17 * 37)
+ initScore) * 37
+ hardScore) * 37
+ mediumScore) * 37
+ softScore;
}
@Override
public int compareTo(HardMediumSoftScore other) {
// A direct implementation (instead of CompareToBuilder) to avoid dependencies
if (initScore != other.getInitScore()) {
return initScore < other.getInitScore() ? -1 : 1;
} else if (hardScore != other.getHardScore()) {
return hardScore < other.getHardScore() ? -1 : 1;
} else if (mediumScore != other.getMediumScore()) {
return mediumScore < other.getMediumScore() ? -1 : 1;
} else {
return Integer.compare(softScore, other.getSoftScore());
}
}
@Override
public String toShortString() {
return buildShortString((n) -> ((Integer) n).intValue() != 0, HARD_LABEL, MEDIUM_LABEL, SOFT_LABEL);
}
@Override
public String toString() {
return getInitPrefix() + hardScore + HARD_LABEL + "/" + mediumScore + MEDIUM_LABEL + "/" + softScore + SOFT_LABEL;
}
@Override
public boolean isCompatibleArithmeticArgument(Score otherScore) {
return otherScore instanceof HardMediumSoftScore;
}
}