Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
TopologicalLinearEquationSolver.cpp
Go to the documentation of this file.
2
11
12namespace storm {
13namespace solver {
14
15template<typename ValueType>
17 // Intentionally left empty.
18}
19
20template<typename ValueType>
24
25template<typename ValueType>
29
30template<typename ValueType>
36
37template<typename ValueType>
39 localA = std::make_unique<storm::storage::SparseMatrix<ValueType>>(std::move(A));
40 this->A = localA.get();
41 clearCache();
42}
43
44template<typename ValueType>
45storm::Environment TopologicalLinearEquationSolver<ValueType>::getEnvironmentForUnderlyingSolver(storm::Environment const& env, bool adaptPrecision) const {
46 storm::Environment subEnv(env);
47 subEnv.solver().setLinearEquationSolverType(env.solver().topological().getUnderlyingEquationSolverType(),
49 if (adaptPrecision) {
50 STORM_LOG_ASSERT(this->longestSccChainSize, "Did not compute the longest SCC chain size although it is needed.");
51 auto subEnvPrec = subEnv.solver().getPrecisionOfLinearEquationSolver(subEnv.solver().getLinearEquationSolverType());
52 storm::RationalNumber const prec = subEnvPrec.first.get();
53 storm::RationalNumber divisor = storm::utility::convertNumber<storm::RationalNumber>(this->longestSccChainSize.get());
54 if (subEnvPrec.second.is_initialized() && subEnvPrec.second.get()) {
55 // Relative errors compose multiplicatively along a chain, so dividing by its length alone leaves
56 // (1 + prec/k)^k - 1 > prec. Dividing by k(1 + prec) as well brings the product back below prec.
58 }
59 subEnv.solver().setLinearEquationSolverPrecision(static_cast<storm::RationalNumber>(prec / divisor));
60 }
61 return subEnv;
62}
63
64template<typename ValueType>
66 std::vector<ValueType> const& b) const {
67 // For sound computations we need to increase the precision in each SCC
68 bool needAdaptPrecision =
69 env.solver().isForceSoundness() &&
71
72 if (!this->sortedSccDecomposition || (needAdaptPrecision && !this->longestSccChainSize)) {
73 STORM_LOG_TRACE("Creating SCC decomposition.");
74 storm::utility::Stopwatch sccSw(true);
75 createSortedSccDecomposition(needAdaptPrecision);
76 sccSw.stop();
77 STORM_LOG_INFO("SCC decomposition computed in "
78 << sccSw << ". Found " << this->sortedSccDecomposition->size() << " SCC(s) containing a total of " << x.size()
79 << " states. Average SCC size is "
80 << static_cast<double>(this->getMatrixRowCount()) / static_cast<double>(this->sortedSccDecomposition->size()) << ".");
81 }
82
83 // We do not need to adapt the precision if all SCCs are trivial (i.e., the system is acyclic)
84 needAdaptPrecision = needAdaptPrecision && (this->sortedSccDecomposition->size() != this->getMatrixRowCount());
85
86 storm::Environment sccSolverEnvironment = getEnvironmentForUnderlyingSolver(env, needAdaptPrecision);
87
88 if (this->longestSccChainSize) {
89 STORM_LOG_INFO("Longest SCC chain size is " << this->longestSccChainSize.get() << ".");
90 }
91
92 // Handle the case where there is just one large SCC
93 bool returnValue = true;
94 bool aborted = false;
95 // Trivial SCCs are settled by substitution, so they only ever inherit the error of their successors. SCCs come
96 // in topological order, so conjoining as we go is enough.
97 bool allSccsExact = true;
98 bool allSccsPrecise = true;
99 if (this->sortedSccDecomposition->size() == 1) {
100 if (auto const& scc = *this->sortedSccDecomposition->begin(); scc.size() == 1) {
101 // Catch the trivial case where the whole system is just a single state.
102 returnValue = solveTrivialScc(*scc.begin(), x, b);
103 } else {
104 returnValue = solveFullyConnectedEquationSystem(sccSolverEnvironment, x, b);
105 allSccsExact = this->sccSolver->hasExactSolutionBounds();
106 allSccsPrecise = lastSccMetPrecision(sccSolverEnvironment, x);
107 }
108 } else {
109 // Solve each SCC individually
110 std::optional<storm::storage::BitVector> newRelevantValues;
111 if (env.solver().topological().isExtendRelevantValues() && this->hasRelevantValues() &&
112 this->sortedSccDecomposition->size() < this->A->getRowGroupCount()) {
113 newRelevantValues = this->getRelevantValues();
114 // Extend the relevant values towards those that have an incoming transition from another SCC
115 std::vector<uint64_t> rowGroupToScc = this->sortedSccDecomposition->computeStateToSccIndexMap(this->A->getRowGroupCount());
116 for (uint64_t rowGroup = 0; rowGroup < this->A->getRowGroupCount(); ++rowGroup) {
117 auto currScc = rowGroupToScc[rowGroup];
118 for (auto const& successor : this->A->getRowGroup(rowGroup)) {
119 if (rowGroupToScc[successor.getColumn()] != currScc) {
120 newRelevantValues->set(successor.getColumn(), true);
121 }
122 }
123 }
124 }
125 storm::storage::BitVector sccAsBitVector(x.size(), false);
126 uint64_t sccIndex = 0;
128 progress.setMaxCount(x.size());
129 progress.startNewMeasurement(0);
130 for (auto const& scc : *this->sortedSccDecomposition) {
131 if (scc.size() == 1) {
132 returnValue = solveTrivialScc(*scc.begin(), x, b) && returnValue;
133 } else {
134 for (auto const& state : scc) {
135 sccAsBitVector.set(state, true);
136 }
137 bool sccMetPrecision = false;
138 returnValue = solveScc(sccSolverEnvironment, scc, sccAsBitVector, x, b, newRelevantValues, sccMetPrecision) && returnValue;
139 allSccsExact = allSccsExact && this->sccSolver->hasExactSolutionBounds();
140 allSccsPrecise = allSccsPrecise && sccMetPrecision;
141 // clear sccAsBitVector, either by clearing all bits or by clearing the bits of the current SCC (if its small)
142 if (scc.size() * 64 < sccAsBitVector.size()) {
143 for (auto const& state : scc) {
144 sccAsBitVector.set(state, false);
145 }
146 } else {
147 sccAsBitVector.clear();
148 }
149 }
150 ++sccIndex;
151 progress.updateProgress(sccIndex);
153 STORM_LOG_WARN("Topological solver aborted after analyzing " << sccIndex << "/" << this->sortedSccDecomposition->size() << " SCCs.");
154 aborted = true;
155 break;
156 }
157 }
158 }
159
160 if (returnValue && !aborted) {
161 trySetSolutionBounds(env, x, allSccsExact, allSccsPrecise && needAdaptPrecision);
162 }
163
164 if (!this->isCachingEnabled()) {
165 clearCache();
166 }
167
168 return returnValue;
169}
170
171template<typename ValueType>
172void TopologicalLinearEquationSolver<ValueType>::trySetSolutionBounds(Environment const& env, std::vector<ValueType> const& x, bool allSccsExact,
173 bool allSccsPrecise) const {
174 if (allSccsExact) {
175 this->setSolutionBoundsExact(x);
176 return;
177 }
178 if constexpr (std::is_same_v<ValueType, storm::RationalFunction>) {
179 // Precisions are meaningless for rational functions.
180 return;
181 } else {
183 if (!allSccsPrecise || !precision.first.is_initialized()) {
184 return;
185 }
186 this->setSolutionBoundsFromPrecision(x, storm::utility::convertNumber<ValueType>(precision.first.get()),
187 precision.second.is_initialized() && precision.second.get());
188 }
189}
190
191template<typename ValueType>
192bool TopologicalLinearEquationSolver<ValueType>::lastSccMetPrecision(Environment const& sccSolverEnvironment, std::vector<ValueType> const& sccValues) const {
193 if constexpr (std::is_same_v<ValueType, storm::RationalFunction>) {
194 return false;
195 } else {
196 auto const precision = sccSolverEnvironment.solver().getPrecisionOfLinearEquationSolver(sccSolverEnvironment.solver().getLinearEquationSolverType());
197 if (!precision.first.is_initialized()) {
198 return false;
199 }
200 return this->sccSolver->getSolutionBounds().isWithinPrecision(sccValues, storm::utility::convertNumber<ValueType>(precision.first.get()),
201 precision.second.is_initialized() && precision.second.get());
202 }
203}
204
205template<typename ValueType>
206void TopologicalLinearEquationSolver<ValueType>::createSortedSccDecomposition(bool needLongestChainSize) const {
207 // Obtain the scc decomposition
208 this->sortedSccDecomposition = std::make_unique<storm::storage::StronglyConnectedComponentDecomposition<ValueType>>(
209 *this->A, storm::storage::StronglyConnectedComponentDecompositionOptions().forceTopologicalSort().computeSccDepths(needLongestChainSize));
210 if (needLongestChainSize) {
211 this->longestSccChainSize = this->sortedSccDecomposition->getMaxSccDepth() + 1;
212 }
213}
214
215template<typename ValueType>
216bool TopologicalLinearEquationSolver<ValueType>::solveTrivialScc(uint64_t const& sccState, std::vector<ValueType>& globalX,
217 std::vector<ValueType> const& globalB) const {
218 ValueType& xi = globalX[sccState];
219 xi = globalB[sccState];
220 bool hasDiagonalEntry = false;
222 for (auto const& entry : this->A->getRow(sccState)) {
223 if (entry.getColumn() == sccState) {
224 STORM_LOG_ASSERT(!storm::utility::isOne(entry.getValue()), "Diagonal entry of fix point system has value 1.");
225 hasDiagonalEntry = true;
226 denominator = storm::utility::one<ValueType>() - entry.getValue();
227 } else {
228 xi += entry.getValue() * globalX[entry.getColumn()];
229 }
230 }
231
232 if (hasDiagonalEntry) {
235 "State " << sccState << " has a selfloop with probability '1-(" << denominator << ")'. This could be an indication for numerical issues.");
236 if (storm::utility::isZero(denominator)) {
237 STORM_LOG_THROW(storm::utility::isZero(xi), storm::exceptions::InvalidOperationException, "The equation system has no solution.");
238 } else {
239 xi /= denominator;
240 }
241 }
242 return true;
243}
244
245template<typename ValueType>
246bool TopologicalLinearEquationSolver<ValueType>::solveFullyConnectedEquationSystem(storm::Environment const& sccSolverEnvironment, std::vector<ValueType>& x,
247 std::vector<ValueType> const& b) const {
248 if (!this->sccSolver) {
249 this->sccSolver = GeneralLinearEquationSolverFactory<ValueType>().create(sccSolverEnvironment);
250 this->sccSolver->setCachingEnabled(true);
251 }
252 if (this->hasRelevantValues()) {
253 this->sccSolver->setRelevantValues(this->getRelevantValues());
254 }
255 this->sccSolver->setBoundsFromOtherSolver(*this);
256 if (this->sccSolver->getEquationProblemFormat(sccSolverEnvironment) == LinearEquationSolverProblemFormat::EquationSystem) {
257 // Convert the matrix to an equation system. Note that we need to insert diagonal entries.
258 storm::storage::SparseMatrix<ValueType> eqSysA(*this->A, true);
259 eqSysA.convertToEquationSystem();
260 this->sccSolver->setMatrix(std::move(eqSysA));
261 } else {
262 this->sccSolver->setMatrix(*this->A);
263 }
264
265 return this->sccSolver->solveEquations(sccSolverEnvironment, x, b);
266}
267
268template<typename ValueType>
269bool TopologicalLinearEquationSolver<ValueType>::solveScc(storm::Environment const& sccSolverEnvironment, storm::storage::StronglyConnectedComponent const& scc,
270 storm::storage::BitVector const& sccAsBitVector, std::vector<ValueType>& globalX,
271 std::vector<ValueType> const& globalB,
272 std::optional<storm::storage::BitVector> const& globalRelevantValues, bool& metPrecision) const {
273 // Restricts a vector indexed by states to the states of the scc
274 auto restrictToScc = [&scc](std::vector<ValueType> const& globalVector) {
275 std::vector<ValueType> result;
276 result.reserve(scc.size());
277 for (uint64_t const state : scc) {
278 result.push_back(globalVector[state]);
279 }
280 return result;
281 };
282
283 // Set up the SCC solver
284 if (!this->sccSolver) {
285 this->sccSolver = GeneralLinearEquationSolverFactory<ValueType>().create(sccSolverEnvironment);
286 this->sccSolver->setCachingEnabled(true);
287 }
288 if (!this->sccSubmatrixBuilder) {
289 this->sccSubmatrixBuilder = std::make_unique<storm::storage::SubmatrixBuilder<ValueType>>(*this->A);
290 }
291 if (globalRelevantValues) {
292 storm::storage::BitVector sccRelevantValues(scc.size(), false);
293 uint64_t sccState = 0;
294 for (uint64_t const state : scc) {
295 sccRelevantValues.set(sccState++, globalRelevantValues->get(state));
296 }
297 this->sccSolver->setRelevantValues(std::move(sccRelevantValues));
298 }
299
300 // Matrix
301 bool asEquationSystem = this->sccSolver->getEquationProblemFormat(sccSolverEnvironment) == LinearEquationSolverProblemFormat::EquationSystem;
302 storm::storage::SparseMatrix<ValueType> sccA = this->sccSubmatrixBuilder->getByRowGroupConstraint(scc, scc, asEquationSystem);
303 if (asEquationSystem) {
305 }
306 this->sccSolver->setMatrix(std::move(sccA));
307
308 // x Vector
309 auto sccX = restrictToScc(globalX);
310
311 // b Vector
312 std::vector<ValueType> sccB;
313 sccB.reserve(scc.size());
314 for (uint64_t const row : scc) {
315 ValueType bi = globalB[row];
316 for (auto const& entry : this->A->getRow(row)) {
317 if (!sccAsBitVector.get(entry.getColumn())) {
318 bi += entry.getValue() * globalX[entry.getColumn()];
319 }
320 }
321 sccB.push_back(std::move(bi));
322 }
323
324 // lower/upper bounds
325 if (this->hasLowerBound(storm::solver::AbstractEquationSolver<ValueType>::BoundType::Global)) {
326 this->sccSolver->setLowerBound(this->getLowerBound());
327 } else if (this->hasLowerBound(storm::solver::AbstractEquationSolver<ValueType>::BoundType::Local)) {
328 this->sccSolver->setLowerBounds(restrictToScc(this->getLowerBounds()));
329 }
330 if (this->hasUpperBound(storm::solver::AbstractEquationSolver<ValueType>::BoundType::Global)) {
331 this->sccSolver->setUpperBound(this->getUpperBound());
332 } else if (this->hasUpperBound(storm::solver::AbstractEquationSolver<ValueType>::BoundType::Local)) {
333 this->sccSolver->setUpperBounds(restrictToScc(this->getUpperBounds()));
334 }
335
336 bool returnvalue = this->sccSolver->solveEquations(sccSolverEnvironment, sccX, sccB);
337 metPrecision = lastSccMetPrecision(sccSolverEnvironment, sccX);
338
339 // Set solution
340 uint64_t sccState = 0;
341 for (uint64_t const state : scc) {
342 globalX[state] = std::move(sccX[sccState++]);
343 }
344 return returnvalue;
345}
346
347template<typename ValueType>
351
352template<typename ValueType>
354 // Return the requirements of the underlying solver
355 return GeneralLinearEquationSolverFactory<ValueType>().getRequirements(getEnvironmentForUnderlyingSolver(env));
356}
357
358template<typename ValueType>
360 sortedSccDecomposition.reset();
361 longestSccChainSize = boost::none;
362 sccSolver.reset();
363 sccSubmatrixBuilder.reset();
365}
366
367template<typename ValueType>
368uint64_t TopologicalLinearEquationSolver<ValueType>::getMatrixRowCount() const {
369 return this->A->getRowCount();
370}
371
372template<typename ValueType>
373uint64_t TopologicalLinearEquationSolver<ValueType>::getMatrixColumnCount() const {
374 return this->A->getColumnCount();
375}
376
377template<typename ValueType>
378std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> TopologicalLinearEquationSolverFactory<ValueType>::create(Environment const& env) const {
379 auto solver = std::make_unique<storm::solver::TopologicalLinearEquationSolver<ValueType>>();
380 solver->setShowProgress(env.solver().isVerboseSet(), env.solver().getShowProgressDelay());
381 return solver;
382}
383
384template<typename ValueType>
385std::unique_ptr<LinearEquationSolverFactory<ValueType>> TopologicalLinearEquationSolverFactory<ValueType>::clone() const {
386 return std::make_unique<TopologicalLinearEquationSolverFactory<ValueType>>(*this);
387}
388
389// Explicitly instantiate the linear equation solver.
392
395
398
399} // namespace solver
400} // namespace storm
SolverEnvironment & solver()
TopologicalSolverEnvironment & topological()
uint64_t getShowProgressDelay() const
std::pair< boost::optional< storm::RationalNumber >, boost::optional< bool > > getPrecisionOfLinearEquationSolver(storm::solver::EquationSolverType const &solverType) const
storm::solver::EquationSolverType const & getUnderlyingEquationSolverType() const
storm::storage::BitVector const & getRelevantValues() const
virtual std::unique_ptr< LinearEquationSolver< ValueType > > create(Environment const &env) const override
Creates an equation solver with the current settings, but without a matrix.
LinearEquationSolverRequirements getRequirements(Environment const &env) const
Retrieves the requirements of the solver if it was created with the current settings.
bool isCachingEnabled() const
Retrieves whether some of the generated data during solver calls should be cached.
virtual std::unique_ptr< LinearEquationSolverFactory< ValueType > > clone() const override
Creates a copy of this factory.
virtual std::unique_ptr< storm::solver::LinearEquationSolver< ValueType > > create(Environment const &env) const override
Creates an equation solver with the current settings, but without a matrix.
virtual LinearEquationSolverProblemFormat getEquationProblemFormat(storm::Environment const &env) const override
Retrieves the format in which this solver expects to solve equations.
virtual bool internalSolveEquations(storm::Environment const &env, std::vector< ValueType > &x, std::vector< ValueType > const &b) const override
virtual void setMatrix(storm::storage::SparseMatrix< ValueType > const &A) override
virtual LinearEquationSolverRequirements getRequirements(Environment const &env) const override
Retrieves the requirements of the solver under 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.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
size_t size() const
Retrieves the number of bits this bit vector can store.
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.
void convertToEquationSystem()
Transforms the matrix into an equation system.
std::size_t size() const
Retrieves the number of states in this SCC.
A class that provides convenience operations to display run times.
bool updateProgress(uint64_t count)
Updates the progress to the current count and logs it (on the progress log channel) if the delay pass...
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:13
void stop()
Stop stopwatch and add measured time to total time.
Definition Stopwatch.cpp:42
#define STORM_LOG_INFO(message)
Definition logging.h:34
#define STORM_LOG_WARN(message)
Definition logging.h:35
#define STORM_LOG_TRACE(message)
Definition logging.h:22
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
#define STORM_LOG_WARN_COND_DEBUG(cond, message)
Definition macros.h:16
SFTBDDChecker::ValueType ValueType
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
bool isOne(ValueType const &a)
Definition constants.cpp:39
NumberTraits< RationalType >::IntegerType denominator(RationalType const &number)
bool isAlmostZero(ValueType const &a)
Definition constants.cpp:98
bool isZero(ValueType const &a)
Definition constants.cpp:44
ValueType one()
Definition constants.cpp:21
TargetType convertNumber(SourceType const &number)
static const bool IsExact