Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
TopologicalMinMaxLinearEquationSolver.cpp
Go to the documentation of this file.
2
5
15
16namespace storm {
17namespace solver {
18
19template<typename ValueType, typename SolutionType>
23
24template<typename ValueType, typename SolutionType>
29
30template<typename ValueType, typename SolutionType>
35
36template<typename ValueType, typename SolutionType>
37storm::Environment TopologicalMinMaxLinearEquationSolver<ValueType, SolutionType>::getEnvironmentForUnderlyingSolver(storm::Environment const& env,
38 bool adaptPrecision) const {
39 storm::Environment subEnv(env);
40 subEnv.solver().minMax().setMethod(env.solver().topological().getUnderlyingMinMaxMethod(),
42 if (adaptPrecision) {
43 STORM_LOG_ASSERT(this->longestSccChainSize, "Did not compute the longest SCC chain size although it is needed.");
44 storm::RationalNumber subEnvPrec =
45 subEnv.solver().minMax().getPrecision() / storm::utility::convertNumber<storm::RationalNumber>(this->longestSccChainSize.get());
46 subEnv.solver().minMax().setPrecision(subEnvPrec);
47 }
48 return subEnv;
49}
50
51template<typename ValueType, typename SolutionType>
53 std::vector<SolutionType>& x,
54 std::vector<ValueType> const& b) const {
55 STORM_LOG_ASSERT(x.size() == this->A->getRowGroupCount(), "Provided x-vector has invalid size.");
56 STORM_LOG_ASSERT(b.size() == this->A->getRowCount(), "Provided b-vector has invalid size.");
57
58 // For sound computations we need to increase the precision in each SCC
59 bool needAdaptPrecision = env.solver().isForceSoundness();
60
61 if (!this->sortedSccDecomposition || (needAdaptPrecision && !this->longestSccChainSize)) {
62 STORM_LOG_TRACE("Creating SCC decomposition.");
63 storm::utility::Stopwatch sccSw(true);
64 createSortedSccDecomposition(needAdaptPrecision);
65 sccSw.stop();
66 STORM_LOG_INFO("SCC decomposition computed in "
67 << sccSw << ". Found " << this->sortedSccDecomposition->size() << " SCC(s) containing a total of " << x.size()
68 << " states. Average SCC size is "
69 << static_cast<double>(this->A->getRowGroupCount()) / static_cast<double>(this->sortedSccDecomposition->size()) << ".");
70 }
71
72 // We do not need to adapt the precision if all SCCs are trivial (i.e., the system is acyclic)
73 needAdaptPrecision = needAdaptPrecision && (this->sortedSccDecomposition->size() != this->A->getRowGroupCount());
74
75 storm::Environment sccSolverEnvironment = getEnvironmentForUnderlyingSolver(env, needAdaptPrecision);
76
77 if (this->longestSccChainSize) {
78 STORM_LOG_INFO("Longest SCC chain size is " << this->longestSccChainSize.get());
79 }
80
81 bool returnValue = true;
82 if (this->sortedSccDecomposition->size() == 1 && (!this->choiceFixedForRowGroup || this->choiceFixedForRowGroup.get().empty())) {
83 // Handle the case where there is just one large SCC, as there are no fixed choices for states, we solve it like this
84 if (auto const& scc = *this->sortedSccDecomposition->begin(); scc.size() == 1) {
85 // Catch the trivial case where the whole system is just a single state.
86 if (this->isTrackSchedulerSet()) {
87 this->schedulerChoices = std::vector<uint64_t>(1);
88 }
89 returnValue = solveTrivialScc(*scc.begin(), dir, x, b);
90 } else {
91 returnValue = solveFullyConnectedEquationSystem(sccSolverEnvironment, dir, x, b);
92 }
93 } else {
94 // Solve each SCC individually
95 if (this->isTrackSchedulerSet()) {
96 if (this->schedulerChoices) {
97 this->schedulerChoices.get().resize(x.size());
98 } else {
99 this->schedulerChoices = std::vector<uint64_t>(x.size());
100 }
101 }
102 std::optional<storm::storage::BitVector> newRelevantValues;
103 if (env.solver().topological().isExtendRelevantValues() && this->hasRelevantValues() &&
104 this->sortedSccDecomposition->size() < this->A->getRowGroupCount()) {
105 newRelevantValues = this->getRelevantValues();
106 // Extend the relevant values towards those that have an incoming transition from another SCC
107 std::vector<uint64_t> rowGroupToScc = this->sortedSccDecomposition->computeStateToSccIndexMap(this->A->getRowGroupCount());
108 for (uint64_t rowGroup = 0; rowGroup < this->A->getRowGroupCount(); ++rowGroup) {
109 auto currScc = rowGroupToScc[rowGroup];
110 for (auto const& successor : this->A->getRowGroup(rowGroup)) {
111 if (rowGroupToScc[successor.getColumn()] != currScc) {
112 newRelevantValues->set(successor.getColumn(), true);
113 }
114 }
115 }
116 }
117 storm::storage::BitVector sccRowGroupsAsBitVector(x.size(), false);
118 storm::storage::BitVector sccRowsAsBitVector(b.size(), false);
119 uint64_t sccIndex = 0;
120 storm::utility::ProgressMeasurement progress("states");
121 progress.setMaxCount(x.size());
122 progress.startNewMeasurement(0);
123 for (auto const& scc : *this->sortedSccDecomposition) {
124 if (scc.size() == 1) {
125 returnValue = solveTrivialScc(*scc.begin(), dir, x, b) && returnValue;
126 } else {
127 STORM_LOG_TRACE("Solving SCC of size " << scc.size() << ".");
128 sccRowGroupsAsBitVector.clear();
129 sccRowsAsBitVector.clear();
130 for (auto const& group : scc) { // Group refers to state
131 sccRowGroupsAsBitVector.set(group, true);
132
133 if (!this->choiceFixedForRowGroup || !this->choiceFixedForRowGroup.get()[group]) {
134 for (uint64_t row = this->A->getRowGroupIndices()[group]; row < this->A->getRowGroupIndices()[group + 1]; ++row) {
135 sccRowsAsBitVector.set(row, true);
136 }
137 } else {
138 auto row = this->A->getRowGroupIndices()[group] + this->getInitialScheduler()[group];
139 sccRowsAsBitVector.set(row, true);
140 STORM_LOG_TRACE("Fixing state " << group << " to choice " << this->getInitialScheduler()[group] << ".");
141 }
142 }
143 returnValue = solveScc(sccSolverEnvironment, dir, sccRowGroupsAsBitVector, sccRowsAsBitVector, x, b, newRelevantValues) && returnValue;
144 }
145 ++sccIndex;
146 progress.updateProgress(sccIndex);
148 STORM_LOG_WARN("Topological solver aborted after analyzing " << sccIndex << "/" << this->sortedSccDecomposition->size() << " SCCs.");
149 break;
150 }
151 }
152 }
153
154 if (!this->isCachingEnabled()) {
155 clearCache();
156 }
157
158 return returnValue;
159}
160
161template<typename ValueType, typename SolutionType>
162void TopologicalMinMaxLinearEquationSolver<ValueType, SolutionType>::createSortedSccDecomposition(bool needLongestChainSize) const {
163 // Obtain the scc decomposition
164 this->sortedSccDecomposition = std::make_unique<storm::storage::StronglyConnectedComponentDecomposition<ValueType>>(
166 if (needLongestChainSize) {
167 this->longestSccChainSize = this->sortedSccDecomposition->getMaxSccDepth() + 1;
168 }
169}
170
171template<typename ValueType, typename SolutionType>
172bool TopologicalMinMaxLinearEquationSolver<ValueType, SolutionType>::solveTrivialScc(uint64_t const& sccState, OptimizationDirection dir,
173 std::vector<SolutionType>& globalX,
174 std::vector<ValueType> const& globalB) const {
175 SolutionType& xi = globalX[sccState];
176 if (this->choiceFixedForRowGroup && this->choiceFixedForRowGroup.get()[sccState]) {
177 // if the choice in the scheduler is fixed we only update for the fixed choice
178 uint_fast64_t row = this->A->getRowGroupIndices()[sccState] + this->getInitialScheduler()[sccState];
179 ValueType rowValue = globalB[row];
180 bool hasDiagonalEntry = false;
181 ValueType denominator;
182 for (auto const& entry : this->A->getRow(row)) {
183 if (entry.getColumn() == sccState) {
184 hasDiagonalEntry = true;
185 denominator = storm::utility::one<ValueType>() - entry.getValue();
186 } else {
187 rowValue += entry.getValue() * globalX[entry.getColumn()];
188 }
189 }
190 if (hasDiagonalEntry) {
193 "State " << sccState << " has a selfloop with probability '1-(" << denominator << ")'. This could be an indication for numerical issues.");
194 if (storm::utility::isZero(denominator)) {
195 // In this case we have a selfloop on this state. This can never an optimal choice:
196 // When minimizing, we are looking for the largest fixpoint (which will never be attained by this action)
197 // When maximizing, this choice reflects probability zero (non-optimal) or reward infinity (should already be handled during preprocessing).
198 } else {
199 rowValue /= denominator;
200 }
201 }
202 xi = std::move(rowValue);
203 } else {
204 bool firstRow = true;
205 uint64_t bestRow;
206 for (uint64_t row = this->A->getRowGroupIndices()[sccState]; row < this->A->getRowGroupIndices()[sccState + 1]; ++row) {
207 ValueType rowValue = globalB[row];
208 bool hasDiagonalEntry = false;
210 for (auto const& entry : this->A->getRow(row)) {
211 if (entry.getColumn() == sccState) {
212 hasDiagonalEntry = true;
213 denominator = storm::utility::one<ValueType>() - entry.getValue();
214 } else {
215 rowValue += entry.getValue() * globalX[entry.getColumn()];
216 }
217 }
218 if (hasDiagonalEntry) {
221 "State " << sccState << " has a selfloop with probability '1-(" << denominator << ")'. This could be an indication for numerical issues.");
222 if (storm::utility::isZero(denominator)) {
223 // In this case we have a selfloop on this state. This can never be an optimal choice:
224 // When minimizing, we are looking for the largest fixpoint (which will never be attained by this action)
225 // When maximizing, this choice reflects probability zero (non-optimal) or reward infinity (should already be handled during preprocessing).
226 continue;
227 } else {
228 rowValue /= denominator;
229 }
230 }
231 if (firstRow) {
232 xi = std::move(rowValue);
233 bestRow = row;
234 firstRow = false;
235 } else {
236 if (minimize(dir)) {
237 if (rowValue < xi) {
238 xi = std::move(rowValue);
239 bestRow = row;
240 }
241 } else {
242 if (rowValue > xi) {
243 xi = std::move(rowValue);
244 bestRow = row;
245 }
246 }
247 }
248 }
249 if (this->isTrackSchedulerSet()) {
250 this->schedulerChoices.get()[sccState] = bestRow - this->A->getRowGroupIndices()[sccState];
251 }
252 STORM_LOG_THROW(!firstRow, storm::exceptions::UnexpectedException, "Empty row group in MinMax equation system.");
253 }
254 return true;
255}
256
257template<typename ValueType, typename SolutionType>
258bool TopologicalMinMaxLinearEquationSolver<ValueType, SolutionType>::solveFullyConnectedEquationSystem(storm::Environment const& sccSolverEnvironment,
259 OptimizationDirection dir, std::vector<SolutionType>& x,
260 std::vector<ValueType> const& b) const {
261 STORM_LOG_ASSERT(!this->choiceFixedForRowGroup || this->choiceFixedForRowGroup.get().empty(),
262 "Expecting no fixed choices for states when solving the fully connected equation system");
263 if (!this->sccSolver) {
264 this->sccSolver = GeneralMinMaxLinearEquationSolverFactory<ValueType>().create(sccSolverEnvironment);
265 this->sccSolver->setCachingEnabled(true);
266 }
267 this->sccSolver->setMatrix(*this->A);
268 this->sccSolver->setHasUniqueSolution(this->hasUniqueSolution());
269 this->sccSolver->setHasNoEndComponents(this->hasNoEndComponents());
270 this->sccSolver->setTrackScheduler(this->isTrackSchedulerSet());
271 if (this->hasInitialScheduler()) {
272 auto choices = this->getInitialScheduler();
273 this->sccSolver->setInitialScheduler(std::move(choices));
274 }
275 if (this->hasRelevantValues()) {
276 this->sccSolver->setRelevantValues(this->getRelevantValues());
277 }
278 auto req = this->sccSolver->getRequirements(sccSolverEnvironment, dir);
279 this->sccSolver->setBoundsFromOtherSolver(*this);
280 if (req.upperBounds() && this->hasUpperBound()) {
281 req.clearUpperBounds();
282 }
283 if (req.lowerBounds() && this->hasLowerBound()) {
284 req.clearLowerBounds();
285 }
286 if (req.validInitialScheduler() && this->hasInitialScheduler()) {
287 req.clearValidInitialScheduler();
288 }
289 if (req.uniqueSolution() && this->hasUniqueSolution()) {
290 req.clearUniqueSolution();
291 }
292 STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException,
293 "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked.");
294 this->sccSolver->setRequirementsChecked(true);
295
296 bool res = this->sccSolver->solveEquations(sccSolverEnvironment, dir, x, b);
297 if (this->isTrackSchedulerSet()) {
298 this->schedulerChoices = this->sccSolver->getSchedulerChoices();
299 }
300 return res;
301}
302
303template<typename ValueType, typename SolutionType>
304bool TopologicalMinMaxLinearEquationSolver<ValueType, SolutionType>::solveScc(storm::Environment const& sccSolverEnvironment, OptimizationDirection dir,
305 storm::storage::BitVector const& sccRowGroups,
306 storm::storage::BitVector const& sccRows, std::vector<SolutionType>& globalX,
307 std::vector<ValueType> const& globalB,
308 std::optional<storm::storage::BitVector> const& globalRelevantValues) const {
309 // Set up the SCC solver
310 if (!this->sccSolver) {
311 this->sccSolver = GeneralMinMaxLinearEquationSolverFactory<ValueType>().create(sccSolverEnvironment);
312 this->sccSolver->setCachingEnabled(true);
313 }
314 this->sccSolver->setHasUniqueSolution(this->hasUniqueSolution());
315 this->sccSolver->setHasNoEndComponents(this->hasNoEndComponents());
316 this->sccSolver->setTrackScheduler(this->isTrackSchedulerSet());
317 if (globalRelevantValues) {
318 this->sccSolver->setRelevantValues((*globalRelevantValues) % sccRowGroups);
319 }
320
321 storm::storage::SparseMatrix<ValueType> sccA;
322 if (this->choiceFixedForRowGroup) {
323 // Obtain choiceFixedForState bitvector containing only the states of the scc.
324 storm::storage::BitVector choiceFixedForStateSCC = this->choiceFixedForRowGroup.get() % sccRowGroups;
325 sccA = this->A->getSubmatrix(false, sccRows, sccRowGroups);
326
327 // initial scheduler
328 if (this->hasInitialScheduler()) {
329 std::vector<uint_fast64_t> sccInitChoices = storm::utility::vector::filterVector(this->getInitialScheduler(), sccRowGroups);
330 // As we removed the entries where the choice was fixed, we need to change the scheduler.
331 // We set the scheduler to 0 for those states.
332 storm::utility::vector::setVectorValues<uint_fast64_t>(sccInitChoices, choiceFixedForStateSCC, 0);
333 this->sccSolver->setInitialScheduler(std::move(sccInitChoices));
334 }
335
336 } else {
337 sccA = this->A->getSubmatrix(true, sccRowGroups, sccRowGroups);
338
339 // initial scheduler
340 if (this->hasInitialScheduler()) {
341 auto sccInitChoices = storm::utility::vector::filterVector(this->getInitialScheduler(), sccRowGroups);
342 this->sccSolver->setInitialScheduler(std::move(sccInitChoices));
343 }
344 }
345
346 this->sccSolver->setMatrix(std::move(sccA));
347
348 // x Vector
349 auto sccX = storm::utility::vector::filterVector(globalX, sccRowGroups);
350
351 // b Vector
352 std::vector<ValueType> sccB;
353 sccB.reserve(sccRows.getNumberOfSetBits());
354 for (auto row : sccRows) {
355 ValueType bi = globalB[row];
356 for (auto const& entry : this->A->getRow(row)) {
357 if (!sccRowGroups.get(entry.getColumn())) {
358 bi += entry.getValue() * globalX[entry.getColumn()];
359 }
360 }
361 sccB.push_back(std::move(bi));
362 }
363
364 auto req = this->sccSolver->getRequirements(sccSolverEnvironment, dir);
365 this->sccSolver->clearBounds();
366 // lower/upper bounds
368 this->sccSolver->setLowerBound(this->getLowerBound());
369 req.clearLowerBounds();
371 this->sccSolver->setLowerBounds(storm::utility::vector::filterVector(this->getLowerBounds(), sccRowGroups));
372 req.clearLowerBounds();
373 }
375 this->sccSolver->setUpperBound(this->getUpperBound());
376 req.clearUpperBounds();
378 this->sccSolver->setUpperBounds(storm::utility::vector::filterVector(this->getUpperBounds(), sccRowGroups));
379 req.clearUpperBounds();
380 }
381
382 // Requirements
383 if (req.validInitialScheduler() && (this->hasInitialScheduler() || this->hasNoEndComponents())) {
384 req.clearValidInitialScheduler();
385 }
386 if (req.uniqueSolution() && this->hasUniqueSolution()) {
387 req.clearUniqueSolution();
388 }
389 STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException,
390 "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked.");
391 this->sccSolver->setRequirementsChecked(true);
392
393 // Invoke scc solver
394 bool res = this->sccSolver->solveEquations(sccSolverEnvironment, dir, sccX, sccB);
395
396 // Set Scheduler choices
397 if (this->isTrackSchedulerSet()) {
398 storm::utility::vector::setVectorValues(this->schedulerChoices.get(), sccRowGroups, this->sccSolver->getSchedulerChoices());
399 }
400
401 // Set solution
402 storm::utility::vector::setVectorValues(globalX, sccRowGroups, sccX);
403
404 return res;
405}
406
407template<typename ValueType, typename SolutionType>
409 Environment const& env, boost::optional<storm::solver::OptimizationDirection> const& direction, bool const& hasInitialScheduler) const {
410 // Return the requirements of the underlying solver
411 return GeneralMinMaxLinearEquationSolverFactory<ValueType>().getRequirements(getEnvironmentForUnderlyingSolver(env), this->hasUniqueSolution(),
413 this->isTrackSchedulerSet());
414}
415
416template<typename ValueType, typename SolutionType>
418 sortedSccDecomposition.reset();
419 longestSccChainSize = boost::none;
420 sccSolver.reset();
421 auxiliaryRowGroupVector.reset();
423}
424
425// Explicitly instantiate the min max linear equation solver.
428// TODO implement topological mode for intervals
429// template class TopologicalMinMaxLinearEquationSolver<storm::Interval, double>;
430
431} // namespace solver
432} // namespace storm
SolverEnvironment & solver()
TopologicalSolverEnvironment & topological()
storm::solver::MinMaxMethod const & getUnderlyingMinMaxMethod() const
storm::storage::BitVector const & getRelevantValues() const
Retrieves the relevant values (if there are any).
virtual std::unique_ptr< MinMaxLinearEquationSolver< ValueType, SolutionType > > create(Environment const &env) const override
MinMaxLinearEquationSolverRequirements getRequirements(Environment const &env, bool hasUniqueSolution=false, bool hasNoEndComponents=false, boost::optional< storm::solver::OptimizationDirection > const &direction=boost::none, bool hasInitialScheduler=false, bool trackScheduler=false) const
Retrieves the requirements of the solver that would be created when calling create() right now.
virtual bool internalSolveEquations(storm::Environment const &env, OptimizationDirection d, std::vector< SolutionType > &x, std::vector< ValueType > const &b) const override
virtual void clearCache() const override
Clears the currently cached data that has been stored during previous calls of the solver.
virtual MinMaxLinearEquationSolverRequirements getRequirements(Environment const &env, boost::optional< storm::solver::OptimizationDirection > const &direction=boost::none, bool const &hasInitialScheduler=false) const override
Retrieves the requirements of this solver for solving equations with the current settings.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
void clear()
Removes all set bits from the bit vector.
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
A class that holds a possibly non-square matrix in the compressed row storage format.
SparseMatrix getSubmatrix(bool useGroups, storm::storage::BitVector const &rowConstraint, storm::storage::BitVector const &columnConstraint, bool insertDiagonalEntries=false, storm::storage::BitVector const &makeZeroColumns=storm::storage::BitVector()) const
Creates a submatrix of the current matrix by dropping all rows and columns whose bits are not set to ...
A class that provides convenience operations to display run times.
bool updateProgress(uint64_t count)
Updates the progress to the current count and prints it if the delay passed.
void setMaxCount(uint64_t maxCount)
Sets the maximal possible count.
void startNewMeasurement(uint64_t startCount)
Starts a new measurement, dropping all progress information collected so far.
A class that provides convenience operations to display run times.
Definition Stopwatch.h:14
void stop()
Stop stopwatch and add measured time to total time.
Definition Stopwatch.cpp:42
#define STORM_LOG_INFO(message)
Definition logging.h:24
#define STORM_LOG_WARN(message)
Definition logging.h:25
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
#define STORM_LOG_WARN_COND_DEBUG(cond, message)
Definition macros.h:18
SFTBDDChecker::ValueType ValueType
bool constexpr minimize(OptimizationDirection d)
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
void setVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< T > const &values)
Sets the provided values at the provided positions in the given vector.
Definition vector.h:78
std::vector< Type > filterVector(std::vector< Type > const &in, storm::storage::BitVector const &filter)
Definition vector.h:1060
NumberTraits< RationalType >::IntegerType denominator(RationalType const &number)
bool isAlmostZero(ValueType const &a)
Definition constants.cpp:93
bool isZero(ValueType const &a)
Definition constants.cpp:39
ValueType one()
Definition constants.cpp:19
TargetType convertNumber(SourceType const &number)
static const bool IsExact
StronglyConnectedComponentDecompositionOptions & computeSccDepths(bool value=true)
Sets if scc depths can be retrieved.
StronglyConnectedComponentDecompositionOptions & forceTopologicalSort(bool value=true)
Enforces that the returned SCCs are sorted in a topological order.