/*
* Copyright 2010 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.benchmark.impl.result;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import org.optaplanner.benchmark.config.statistic.ProblemStatisticType;
import org.optaplanner.benchmark.impl.measurement.ScoreDifferencePercentage;
import org.optaplanner.benchmark.impl.ranking.ScoreSubSingleBenchmarkRankingComparator;
import org.optaplanner.benchmark.impl.ranking.SubSingleBenchmarkRankBasedComparator;
import org.optaplanner.benchmark.impl.report.BenchmarkReport;
import org.optaplanner.benchmark.impl.statistic.StatisticUtils;
import org.optaplanner.benchmark.impl.statistic.SubSingleStatistic;
import org.optaplanner.core.api.score.FeasibilityScore;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.config.util.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents 1 benchmark for 1 {@link Solver} configuration for 1 problem instance (data set).
*/
@XStreamAlias("singleBenchmarkResult")
public class SingleBenchmarkResult implements BenchmarkResult {
protected static final transient Logger logger = LoggerFactory.getLogger(SingleBenchmarkResult.class);
@XStreamOmitField // Bi-directional relationship restored through BenchmarkResultIO
private SolverBenchmarkResult solverBenchmarkResult;
@XStreamOmitField // Bi-directional relationship restored through BenchmarkResultIO
private ProblemBenchmarkResult problemBenchmarkResult;
@XStreamImplicit(itemFieldName = "subSingleBenchmarkResult")
private List<SubSingleBenchmarkResult> subSingleBenchmarkResultList = null;
private Long usedMemoryAfterInputSolution = null;
private Integer failureCount = null;
private Score totalScore = null;
private Score averageScore = null;
private SubSingleBenchmarkResult median = null;
private SubSingleBenchmarkResult best = null;
private SubSingleBenchmarkResult worst = null;
private Integer uninitializedSolutionCount = null;
private Integer infeasibleScoreCount = null;
// Not a Score because
// - the squaring would cause overflow for relatively small int and long scores.
// - standard deviation should not be rounded to integer numbers
private double[] standardDeviationDoubles = null;
private long timeMillisSpent = -1L;
private long scoreCalculationCount = -1L;
// ************************************************************************
// Report accumulates
// ************************************************************************
// Compared to winningSingleBenchmarkResult in the same ProblemBenchmarkResult (which might not be the overall favorite)
private Score winningScoreDifference = null;
private ScoreDifferencePercentage worstScoreDifferencePercentage = null;
private Double worstScoreCalculationSpeedDifferencePercentage = null;
// Ranking starts from 0
private Integer ranking = null;
// ************************************************************************
// Constructors and simple getters/setters
// ************************************************************************
public SingleBenchmarkResult(SolverBenchmarkResult solverBenchmarkResult, ProblemBenchmarkResult problemBenchmarkResult) {
this.solverBenchmarkResult = solverBenchmarkResult;
this.problemBenchmarkResult = problemBenchmarkResult;
}
public void initSubSingleStatisticMaps() {
for (SubSingleBenchmarkResult subSingleBenchmarkResult : subSingleBenchmarkResultList) {
subSingleBenchmarkResult.initSubSingleStatisticMap();
}
}
public SolverBenchmarkResult getSolverBenchmarkResult() {
return solverBenchmarkResult;
}
public void setSolverBenchmarkResult(SolverBenchmarkResult solverBenchmarkResult) {
this.solverBenchmarkResult = solverBenchmarkResult;
}
public ProblemBenchmarkResult getProblemBenchmarkResult() {
return problemBenchmarkResult;
}
public void setProblemBenchmarkResult(ProblemBenchmarkResult problemBenchmarkResult) {
this.problemBenchmarkResult = problemBenchmarkResult;
}
public List<SubSingleBenchmarkResult> getSubSingleBenchmarkResultList() {
return subSingleBenchmarkResultList;
}
public void setSubSingleBenchmarkResultList(List<SubSingleBenchmarkResult> subSingleBenchmarkResultList) {
this.subSingleBenchmarkResultList = subSingleBenchmarkResultList;
}
/**
* @return null if {@link PlannerBenchmarkResult#hasMultipleParallelBenchmarks()} return true
*/
public Long getUsedMemoryAfterInputSolution() {
return usedMemoryAfterInputSolution;
}
public void setUsedMemoryAfterInputSolution(Long usedMemoryAfterInputSolution) {
this.usedMemoryAfterInputSolution = usedMemoryAfterInputSolution;
}
public Integer getFailureCount() {
return failureCount;
}
public void setFailureCount(Integer failureCount) {
this.failureCount = failureCount;
}
public long getTimeMillisSpent() {
return timeMillisSpent;
}
public void setTimeMillisSpent(long timeMillisSpent) {
this.timeMillisSpent = timeMillisSpent;
}
public long getScoreCalculationCount() {
return scoreCalculationCount;
}
public void setScoreCalculationCount(long scoreCalculationCount) {
this.scoreCalculationCount = scoreCalculationCount;
}
public Score getWinningScoreDifference() {
return winningScoreDifference;
}
public void setWinningScoreDifference(Score winningScoreDifference) {
this.winningScoreDifference = winningScoreDifference;
}
public ScoreDifferencePercentage getWorstScoreDifferencePercentage() {
return worstScoreDifferencePercentage;
}
public void setWorstScoreDifferencePercentage(ScoreDifferencePercentage worstScoreDifferencePercentage) {
this.worstScoreDifferencePercentage = worstScoreDifferencePercentage;
}
public Double getWorstScoreCalculationSpeedDifferencePercentage() {
return worstScoreCalculationSpeedDifferencePercentage;
}
public void setWorstScoreCalculationSpeedDifferencePercentage(Double worstScoreCalculationSpeedDifferencePercentage) {
this.worstScoreCalculationSpeedDifferencePercentage = worstScoreCalculationSpeedDifferencePercentage;
}
public Integer getRanking() {
return ranking;
}
public void setRanking(Integer ranking) {
this.ranking = ranking;
}
@Override
public Score getAverageScore() {
return averageScore;
}
public void setAverageAndTotalScoreForTesting(Score averageAndTotalScore) {
this.averageScore = averageAndTotalScore;
this.totalScore = averageAndTotalScore;
}
public SubSingleBenchmarkResult getMedian() {
return median;
}
public SubSingleBenchmarkResult getBest() {
return best;
}
public SubSingleBenchmarkResult getWorst() {
return worst;
}
public double[] getStandardDeviationDoubles() {
return standardDeviationDoubles;
}
public Integer getInfeasibleScoreCount() {
return infeasibleScoreCount;
}
public Integer getUninitializedSolutionCount() {
return uninitializedSolutionCount;
}
public Score getTotalScore() {
return totalScore;
}
// ************************************************************************
// Smart getters
// ************************************************************************
/**
* @return never null, filename safe
*/
@Override
public String getName() {
return problemBenchmarkResult.getName() + "_" + solverBenchmarkResult.getName();
}
public File getBenchmarkReportDirectory() {
return problemBenchmarkResult.getBenchmarkReportDirectory();
}
@Override
public boolean hasAllSuccess() {
return failureCount != null && failureCount == 0;
}
public boolean isInitialized() {
return averageScore != null && averageScore.isSolutionInitialized();
}
@Override
public boolean hasAnyFailure() {
return failureCount != null && failureCount != 0;
}
public boolean isScoreFeasible() {
if (averageScore instanceof FeasibilityScore) {
return ((FeasibilityScore) averageScore).isFeasible();
} else {
return true;
}
}
public Long getScoreCalculationSpeed() {
long timeMillisSpent = this.timeMillisSpent;
if (timeMillisSpent == 0L) {
// Avoid divide by zero exception on a fast CPU
timeMillisSpent = 1L;
}
return scoreCalculationCount * 1000L / timeMillisSpent;
}
public boolean isWinner() {
return ranking != null && ranking.intValue() == 0;
}
public SubSingleStatistic getSubSingleStatistic(ProblemStatisticType problemStatisticType) {
return getMedian().getEffectiveSubSingleStatisticMap().get(problemStatisticType);
}
public int getSuccessCount() {
return subSingleBenchmarkResultList.size() - failureCount;
}
public String getStandardDeviationString() {
return StatisticUtils.getStandardDeviationString(standardDeviationDoubles);
}
// ************************************************************************
// Accumulate methods
// ************************************************************************
@Override
public String getResultDirectoryName() {
return solverBenchmarkResult.getName();
}
@Override
public File getResultDirectory() {
return new File(problemBenchmarkResult.getProblemReportDirectory(), getResultDirectoryName());
}
public void makeDirs() {
File singleReportDirectory = getResultDirectory();
singleReportDirectory.mkdirs();
for (SubSingleBenchmarkResult subSingleBenchmarkResult : subSingleBenchmarkResultList) {
subSingleBenchmarkResult.makeDirs();
}
}
public int getSubSingleCount() {
return subSingleBenchmarkResultList.size();
}
public void accumulateResults(BenchmarkReport benchmarkReport) {
for (SubSingleBenchmarkResult subSingleBenchmarkResult : subSingleBenchmarkResultList) {
subSingleBenchmarkResult.accumulateResults(benchmarkReport);
}
determineTotalsAndAveragesAndRanking();
standardDeviationDoubles = StatisticUtils.determineStandardDeviationDoubles(subSingleBenchmarkResultList, averageScore, getSuccessCount());
determineRepresentativeSubSingleBenchmarkResult();
}
private void determineRepresentativeSubSingleBenchmarkResult() {
if (subSingleBenchmarkResultList == null || subSingleBenchmarkResultList.isEmpty()) {
throw new IllegalStateException("Cannot get representative subSingleBenchmarkResult from empty subSingleBenchmarkResultList.");
}
List<SubSingleBenchmarkResult> subSingleBenchmarkResultListCopy = new ArrayList<>(subSingleBenchmarkResultList);
// sort (according to ranking) so that the best subSingle is at index 0
subSingleBenchmarkResultListCopy.sort(new SubSingleBenchmarkRankBasedComparator());
best = subSingleBenchmarkResultListCopy.get(0);
worst = subSingleBenchmarkResultListCopy.get(subSingleBenchmarkResultListCopy.size() - 1);
median = subSingleBenchmarkResultListCopy.get(ConfigUtils.ceilDivide(subSingleBenchmarkResultListCopy.size() - 1, 2));
usedMemoryAfterInputSolution = median.getUsedMemoryAfterInputSolution();
timeMillisSpent = median.getTimeMillisSpent();
scoreCalculationCount = median.getScoreCalculationCount();
}
private void determineTotalsAndAveragesAndRanking() {
failureCount = 0;
boolean firstNonFailure = true;
totalScore = null;
uninitializedSolutionCount = 0;
infeasibleScoreCount = 0;
List<SubSingleBenchmarkResult> successResultList = new ArrayList<>(subSingleBenchmarkResultList);
// Do not rank a SubSingleBenchmarkResult that has a failure
for (Iterator<SubSingleBenchmarkResult> it = successResultList.iterator(); it.hasNext(); ) {
SubSingleBenchmarkResult subSingleBenchmarkResult = it.next();
if (subSingleBenchmarkResult.hasAnyFailure()) {
failureCount++;
it.remove();
} else {
if (!subSingleBenchmarkResult.isInitialized()) {
uninitializedSolutionCount++;
} else if (!subSingleBenchmarkResult.isScoreFeasible()) {
infeasibleScoreCount++;
}
if (firstNonFailure) {
totalScore = subSingleBenchmarkResult.getAverageScore();
firstNonFailure = false;
} else {
totalScore = totalScore.add(subSingleBenchmarkResult.getAverageScore());
}
}
}
if (!firstNonFailure) {
averageScore = totalScore.divide(getSuccessCount());
}
determineRanking(successResultList);
}
private void determineRanking(List<SubSingleBenchmarkResult> rankedSubSingleBenchmarkResultList) {
Comparator<SubSingleBenchmarkResult> subSingleBenchmarkRankingComparator = new ScoreSubSingleBenchmarkRankingComparator();
rankedSubSingleBenchmarkResultList.sort(Collections.reverseOrder(subSingleBenchmarkRankingComparator));
int ranking = 0;
SubSingleBenchmarkResult previousSubSingleBenchmarkResult = null;
int previousSameRankingCount = 0;
for (SubSingleBenchmarkResult subSingleBenchmarkResult : rankedSubSingleBenchmarkResultList) {
if (previousSubSingleBenchmarkResult != null
&& subSingleBenchmarkRankingComparator.compare(previousSubSingleBenchmarkResult, subSingleBenchmarkResult) != 0) {
ranking += previousSameRankingCount;
previousSameRankingCount = 0;
}
subSingleBenchmarkResult.setRanking(ranking);
previousSubSingleBenchmarkResult = subSingleBenchmarkResult;
previousSameRankingCount++;
}
}
// ************************************************************************
// Merger methods
// ************************************************************************
protected static SingleBenchmarkResult createMerge(
SolverBenchmarkResult solverBenchmarkResult, ProblemBenchmarkResult problemBenchmarkResult,
SingleBenchmarkResult oldResult) {
SingleBenchmarkResult newResult = new SingleBenchmarkResult(solverBenchmarkResult, problemBenchmarkResult);
newResult.subSingleBenchmarkResultList = new ArrayList<>(oldResult.getSubSingleBenchmarkResultList().size());
int subSingleBenchmarkIndex = 0;
for (SubSingleBenchmarkResult oldSubResult : oldResult.subSingleBenchmarkResultList) {
SubSingleBenchmarkResult.createMerge(newResult, oldSubResult, subSingleBenchmarkIndex);
subSingleBenchmarkIndex++;
}
newResult.median = oldResult.median;
newResult.best = oldResult.best;
newResult.worst = oldResult.worst;
solverBenchmarkResult.getSingleBenchmarkResultList().add(newResult);
problemBenchmarkResult.getSingleBenchmarkResultList().add(newResult);
return newResult;
}
@Override
public String toString() {
return getName();
}
}