/*
* Copyright 2014 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.hardmediumsoftlong;
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 long 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 HardMediumSoftLongScore extends AbstractScore<HardMediumSoftLongScore>
implements FeasibilityScore<HardMediumSoftLongScore> {
public static final HardMediumSoftLongScore ZERO = new HardMediumSoftLongScore(0, 0L, 0L, 0L);
private static final String HARD_LABEL = "hard";
private static final String MEDIUM_LABEL = "medium";
private static final String SOFT_LABEL = "soft";
public static HardMediumSoftLongScore parseScore(String scoreString) {
String[] scoreTokens = parseScoreTokens(HardMediumSoftLongScore.class, scoreString,
HARD_LABEL, MEDIUM_LABEL, SOFT_LABEL);
int initScore = parseInitScore(HardMediumSoftLongScore.class, scoreString, scoreTokens[0]);
long hardScore = parseLevelAsLong(HardMediumSoftLongScore.class, scoreString, scoreTokens[1]);
long mediumScore = parseLevelAsLong(HardMediumSoftLongScore.class, scoreString, scoreTokens[2]);
long softScore = parseLevelAsLong(HardMediumSoftLongScore.class, scoreString, scoreTokens[3]);
return valueOfUninitialized(initScore, hardScore, mediumScore, softScore);
}
public static HardMediumSoftLongScore valueOfUninitialized(int initScore, long hardScore, long mediumScore, long softScore) {
return new HardMediumSoftLongScore(initScore, hardScore, mediumScore, softScore);
}
public static HardMediumSoftLongScore valueOf(long hardScore, long mediumScore, long softScore) {
return new HardMediumSoftLongScore(0, hardScore, mediumScore, softScore);
}
// ************************************************************************
// Fields
// ************************************************************************
private final long hardScore;
private final long mediumScore;
private final long 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 HardMediumSoftLongScore() {
super(Integer.MIN_VALUE);
hardScore = Long.MIN_VALUE;
mediumScore = Long.MIN_VALUE;
softScore = Long.MIN_VALUE;
}
private HardMediumSoftLongScore(int initScore, long hardScore, long mediumScore, long 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 long 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 long 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 long getSoftScore() {
return softScore;
}
// ************************************************************************
// Worker methods
// ************************************************************************
@Override
public HardMediumSoftLongScore toInitializedScore() {
return initScore == 0 ? this : new HardMediumSoftLongScore(0, hardScore, mediumScore, softScore);
}
@Override
public HardMediumSoftLongScore withInitScore(int newInitScore) {
assertNoInitScore();
return new HardMediumSoftLongScore(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 >= 0L;
}
@Override
public HardMediumSoftLongScore add(HardMediumSoftLongScore augment) {
return new HardMediumSoftLongScore(
initScore + augment.getInitScore(),
hardScore + augment.getHardScore(),
mediumScore + augment.getMediumScore(),
softScore + augment.getSoftScore());
}
@Override
public HardMediumSoftLongScore subtract(HardMediumSoftLongScore subtrahend) {
return new HardMediumSoftLongScore(
initScore - subtrahend.getInitScore(),
hardScore - subtrahend.getHardScore(),
mediumScore - subtrahend.getMediumScore(),
softScore - subtrahend.getSoftScore());
}
@Override
public HardMediumSoftLongScore multiply(double multiplicand) {
return new HardMediumSoftLongScore(
(int) Math.floor(initScore * multiplicand),
(long) Math.floor(hardScore * multiplicand),
(long) Math.floor(mediumScore * multiplicand),
(long) Math.floor(softScore * multiplicand));
}
@Override
public HardMediumSoftLongScore divide(double divisor) {
return new HardMediumSoftLongScore(
(int) Math.floor(initScore / divisor),
(long) Math.floor(hardScore / divisor),
(long) Math.floor(mediumScore / divisor),
(long) Math.floor(softScore / divisor));
}
@Override
public HardMediumSoftLongScore power(double exponent) {
return new HardMediumSoftLongScore(
(int) Math.floor(Math.pow(initScore, exponent)),
(long) Math.floor(Math.pow(hardScore, exponent)),
(long) Math.floor(Math.pow(mediumScore, exponent)),
(long) Math.floor(Math.pow(softScore, exponent)));
}
@Override
public HardMediumSoftLongScore negate() {
return new HardMediumSoftLongScore(-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 HardMediumSoftLongScore) {
HardMediumSoftLongScore other = (HardMediumSoftLongScore) 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
+ Long.valueOf(hardScore).hashCode()) * 37
+ Long.valueOf(mediumScore).hashCode()) * 37
+ Long.valueOf(softScore).hashCode();
}
@Override
public int compareTo(HardMediumSoftLongScore 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 Long.compare(softScore, other.getSoftScore());
}
}
@Override
public String toShortString() {
return buildShortString((n) -> ((Long) n).longValue() != 0L, 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 HardMediumSoftLongScore;
}
}