|
|
@ -76,24 +76,31 @@ namespace storm { |
|
|
|
storm::storage::BitVector probabilisticMaybeStates = ~markovianStates & maybeStates; |
|
|
|
storm::storage::BitVector markovianStatesModMaybeStates = markovianMaybeStates % maybeStates; |
|
|
|
storm::storage::BitVector probabilisticStatesModMaybeStates = probabilisticMaybeStates % maybeStates; |
|
|
|
// Catch the case where this query can be solved by solving the untimed variant instead.
|
|
|
|
// This is the case if there is no Markovian maybe state (e.g. if the initial state is already a psi state) of if the time bound is infinity.
|
|
|
|
if (markovianMaybeStates.empty() || storm::utility::isInfinity(upperTimeBound)) { |
|
|
|
return SparseMarkovAutomatonCslHelper::computeUntilProbabilities<ValueType>(env, dir, transitionMatrix, transitionMatrix.transpose(true), phiStates, psiStates, false, false).values; |
|
|
|
} |
|
|
|
|
|
|
|
boost::optional<storm::storage::BitVector> relevantMaybeStates; |
|
|
|
if (relevantStates) { |
|
|
|
relevantMaybeStates = relevantStates.get() % maybeStates; |
|
|
|
} |
|
|
|
// Catch the case where this is query can be solved by solving the untimed variant instead.
|
|
|
|
// This is the case if there is no Markovian maybe state (e.g. if the initial state is already a psi state) of if the time bound is infinity.
|
|
|
|
if (markovianMaybeStates.empty() || storm::utility::isInfinity(upperTimeBound)) { |
|
|
|
return SparseMarkovAutomatonCslHelper::computeUntilProbabilities<ValueType>(env, dir, transitionMatrix, transitionMatrix.transpose(true), phiStates, psiStates, false, false).values; |
|
|
|
// Store the best solution known so far (useful in cases where the computation gets aborted)
|
|
|
|
std::vector<ValueType> bestKnownSolution; |
|
|
|
if (relevantMaybeStates) { |
|
|
|
bestKnownSolution.resize(relevantStates->size()); |
|
|
|
} |
|
|
|
|
|
|
|
// Get the exit rates restricted to only markovian maybe states.
|
|
|
|
std::vector<ValueType> markovianExitRates = storm::utility::vector::filterVector(exitRateVector, markovianMaybeStates); |
|
|
|
|
|
|
|
// Obtain parameters of the algorithm
|
|
|
|
auto two = storm::utility::convertNumber<ValueType>(2.0); |
|
|
|
// Truncation error
|
|
|
|
ValueType kappa = storm::utility::convertNumber<ValueType>(env.solver().timeBounded().getUnifPlusKappa()); |
|
|
|
// Precision to be achieved
|
|
|
|
ValueType epsilon = storm::utility::convertNumber<ValueType>(2.0) * storm::utility::convertNumber<ValueType>(env.solver().timeBounded().getPrecision()); |
|
|
|
ValueType epsilon = two * storm::utility::convertNumber<ValueType>(env.solver().timeBounded().getPrecision()); |
|
|
|
bool relativePrecision = env.solver().timeBounded().getRelativeTerminationCriterion(); |
|
|
|
// Uniformization rate
|
|
|
|
ValueType lambda = *std::max_element(markovianExitRates.begin(), markovianExitRates.end()); |
|
|
@ -132,7 +139,7 @@ namespace storm { |
|
|
|
uint64_t iteration = 0; |
|
|
|
progressIterations.startNewMeasurement(iteration); |
|
|
|
bool converged = false; |
|
|
|
|
|
|
|
bool abortedInnerIterations = false; |
|
|
|
while (!converged) { |
|
|
|
// Maximal step size
|
|
|
|
uint64_t N = storm::utility::ceil(lambda * upperTimeBound * std::exp(2) - storm::utility::log(kappa * epsilon)); |
|
|
@ -155,8 +162,9 @@ namespace storm { |
|
|
|
progressSteps.setMaxCount(N); |
|
|
|
progressSteps.startNewMeasurement(0); |
|
|
|
bool firstIteration = true; // The first iterations can be irrelevant, because they will only produce zeroes anyway.
|
|
|
|
int64_t k = N; |
|
|
|
// Iteration k = N is always non-relevant
|
|
|
|
for (int64_t k = N - 1; k >= 0; --k) { |
|
|
|
for (--k; k >= 0; --k) { |
|
|
|
|
|
|
|
// Check whether the iteration is relevant, that is, whether it will contribute non-zero values to the overall result
|
|
|
|
if (computeLowerBound) { |
|
|
@ -222,20 +230,36 @@ namespace storm { |
|
|
|
} |
|
|
|
|
|
|
|
progressSteps.updateProgress(N-k); |
|
|
|
if (storm::utility::resources::isTerminate()) { |
|
|
|
abortedInnerIterations = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (computeLowerBound) { |
|
|
|
storm::utility::vector::scaleVectorInPlace(maybeStatesValuesLower, storm::utility::one<ValueType>() / foxGlynnResult.totalWeight); |
|
|
|
} else { |
|
|
|
storm::utility::vector::scaleVectorInPlace(maybeStatesValuesUpper, storm::utility::one<ValueType>() / foxGlynnResult.totalWeight); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (abortedInnerIterations || storm::utility::resources::isTerminate()) { |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
// Check if the lower and upper bound are sufficiently close to each other
|
|
|
|
converged = checkConvergence(maybeStatesValuesLower, maybeStatesValuesUpper, relevantMaybeStates, epsilon, relativePrecision, kappa); |
|
|
|
if (converged) { |
|
|
|
break; |
|
|
|
} |
|
|
|
if (storm::utility::resources::isTerminate()) { |
|
|
|
break; |
|
|
|
|
|
|
|
// Store the best solution we have found so far.
|
|
|
|
if (relevantMaybeStates) { |
|
|
|
auto currentSolIt = bestKnownSolution.begin(); |
|
|
|
for (auto const& state : relevantMaybeStates.get()) { |
|
|
|
// We take the average of the lower and upper bounds
|
|
|
|
*currentSolIt = (maybeStatesValuesLower[state] + maybeStatesValuesUpper[state]) / two; |
|
|
|
++currentSolIt; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -244,7 +268,7 @@ namespace storm { |
|
|
|
|
|
|
|
// Double lambda.
|
|
|
|
ValueType oldLambda = lambda; |
|
|
|
lambda *= storm::utility::convertNumber<ValueType>(2.0); |
|
|
|
lambda *= two; |
|
|
|
STORM_LOG_DEBUG("Increased lambda to " << lambda << "."); |
|
|
|
|
|
|
|
if (relativePrecision) { |
|
|
@ -268,17 +292,24 @@ namespace storm { |
|
|
|
} |
|
|
|
progressIterations.updateProgress(++iteration); |
|
|
|
if (storm::utility::resources::isTerminate()) { |
|
|
|
STORM_LOG_WARN("Aborted unif+ in iteration " << iteration << "."); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// We take the average of the lower and upper bounds
|
|
|
|
auto two = storm::utility::convertNumber<ValueType>(2.0); |
|
|
|
storm::utility::vector::applyPointwise<ValueType, ValueType, ValueType>(maybeStatesValuesLower, maybeStatesValuesUpper, maybeStatesValuesLower, [&two] (ValueType const& a, ValueType const& b) -> ValueType { return (a + b) / two; }); |
|
|
|
|
|
|
|
|
|
|
|
// Prepare the result vector
|
|
|
|
std::vector<ValueType> result(transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>()); |
|
|
|
storm::utility::vector::setVectorValues(result, psiStates, storm::utility::one<ValueType>()); |
|
|
|
storm::utility::vector::setVectorValues(result, maybeStates, maybeStatesValuesLower); |
|
|
|
|
|
|
|
if (abortedInnerIterations && iteration > 1 && relevantMaybeStates && relevantStates) { |
|
|
|
// We should take the stored solution instead of the current (probably more incorrect) lower/upper values
|
|
|
|
storm::utility::vector::setVectorValues(result, maybeStates & relevantStates.get(), bestKnownSolution); |
|
|
|
} else { |
|
|
|
// We take the average of the lower and upper bounds
|
|
|
|
storm::utility::vector::applyPointwise<ValueType, ValueType, ValueType>(maybeStatesValuesLower, maybeStatesValuesUpper, maybeStatesValuesLower, [&two] (ValueType const& a, ValueType const& b) -> ValueType { return (a + b) / two; }); |
|
|
|
|
|
|
|
storm::utility::vector::setVectorValues(result, maybeStates, maybeStatesValuesLower); |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|