Merge lp:~tapaal-ltl/verifypn/ltl-trace-fixes into lp:verifypn

Proposed by Nikolaj Jensen Ulrik
Status: Merged
Approved by: Jiri Srba
Approved revision: 251
Merged at revision: 247
Proposed branch: lp:~tapaal-ltl/verifypn/ltl-trace-fixes
Merge into: lp:verifypn
Diff against target: 521 lines (+356/-34)
7 files modified
include/LTL/Algorithm/ModelChecker.h (+13/-2)
include/PetriEngine/TraceReplay.h (+69/-0)
include/PetriEngine/options.h (+1/-1)
src/LTL/CMakeLists.txt (+1/-1)
src/PetriEngine/CMakeLists.txt (+1/-1)
src/PetriEngine/TraceReplay.cpp (+237/-0)
src/VerifyPN.cpp (+34/-29)
To merge this branch: bzr merge lp:~tapaal-ltl/verifypn/ltl-trace-fixes
Reviewer Review Type Date Requested Status
Jiri Srba Approve
Review via email: mp+406754@code.launchpad.net

Description of the change

Fix misunderstanding of trace formats for LTL traces.

To post a comment you must log in.
251. By Nikolaj Jensen Ulrik

Trace replay now works for non-LTL flows too

Revision history for this message
Jiri Srba (srba) wrote :

Only changes the trace behaviour, consistency check is not needed.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/LTL/Algorithm/ModelChecker.h'
2--- include/LTL/Algorithm/ModelChecker.h 2021-08-03 14:19:42 +0000
3+++ include/LTL/Algorithm/ModelChecker.h 2021-08-06 11:23:06 +0000
4@@ -115,11 +115,22 @@
5 if (traceLevel == TraceLevel::Full) {
6 os << ">";
7 os << std::endl;
8- for (size_t i = 0; i < net->numberOfPlaces(); ++i) {
9+ auto [fpre, lpre] = net->preset(transition);
10+ for(; fpre < lpre; ++fpre) {
11+ if (fpre->inhibitor) {
12+ assert(state.marking()[fpre->place] < fpre->tokens);
13+ continue;
14+ }
15+ for (size_t i = 0; i < fpre->tokens; ++i) {
16+ assert(state.marking()[fpre->place] >= fpre->tokens);
17+ os << tokenIndent << R"(<token age="0" place=")" << net->placeNames()[fpre->place] << "\"/>\n";
18+ }
19+ }
20+ /*for (size_t i = 0; i < net->numberOfPlaces(); ++i) {
21 for (size_t j = 0; j < state.marking()[i]; ++j) {
22 os << tokenIndent << R"(<token age="0" place=")" << net->placeNames()[i] << "\"/>\n";
23 }
24- }
25+ }*/
26 os << indent << "</transition>";
27 }
28 else {
29
30=== removed file 'include/LTL/Replay.h'
31=== added file 'include/PetriEngine/TraceReplay.h'
32--- include/PetriEngine/TraceReplay.h 1970-01-01 00:00:00 +0000
33+++ include/PetriEngine/TraceReplay.h 2021-08-06 11:23:06 +0000
34@@ -0,0 +1,69 @@
35+/* Copyright (C) 2021 Nikolaj J. Ulrik <nikolaj@njulrik.dk>,
36+ * Simon M. Virenfeldt <simon@simwir.dk>
37+ *
38+ * This program is free software: you can redistribute it and/or modify
39+ * it under the terms of the GNU General Public License as published by
40+ * the Free Software Foundation, either version 3 of the License, or
41+ * (at your option) any later version.
42+ *
43+ * This program is distributed in the hope that it will be useful,
44+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
45+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46+ * GNU General Public License for more details.
47+ *
48+ * You should have received a copy of the GNU General Public License
49+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
50+ */
51+
52+#ifndef VERIFYPN_TRACEREPLAY_H
53+#define VERIFYPN_TRACEREPLAY_H
54+
55+#include "PetriEngine/PetriNet.h"
56+#include "PetriEngine/options.h"
57+#include "PetriEngine/PQL/PQL.h"
58+#include "PetriEngine/SuccessorGenerator.h"
59+
60+#include <iostream>
61+#include <utility>
62+#include <rapidxml.hpp>
63+
64+namespace PetriEngine {
65+ class TraceReplay {
66+ public:
67+ TraceReplay(std::istream &is, const PetriEngine::PetriNet *net, const options_t &options);
68+
69+ struct Token {
70+ std::string place;
71+ };
72+
73+ struct Transition {
74+ explicit Transition(std::string id, int buchi) : id(std::move(id)), buchi_state(buchi) {}
75+
76+ std::string id;
77+ int buchi_state;
78+ std::unordered_map<uint32_t, uint32_t> tokens;
79+ };
80+
81+ void parse(std::istream &xml, const PetriEngine::PetriNet *net);
82+
83+ bool replay(const PetriEngine::PetriNet *net, const PetriEngine::PQL::Condition_ptr &cond);
84+
85+ std::vector<Transition> trace;
86+ private:
87+
88+ static constexpr auto DEADLOCK_TRANS = "##deadlock";
89+ void parseRoot(const rapidxml::xml_node<> *pNode);
90+
91+ Transition parseTransition(const rapidxml::xml_node<char> *pNode);
92+
93+ void parseToken(const rapidxml::xml_node<char> *pNode, std::unordered_map<uint32_t, uint32_t> &current_marking);
94+
95+ size_t loop_idx = std::numeric_limits<size_t>::max();
96+ std::unordered_map<std::string, int> transitions;
97+ std::unordered_map<std::string, int> places;
98+ bool _play_trace(const PetriEngine::PetriNet *net, PetriEngine::SuccessorGenerator &successorGenerator);
99+ const options_t &options;
100+ };
101+}
102+
103+#endif //VERIFYPN_TRACEREPLAY_H
104
105=== modified file 'include/PetriEngine/options.h'
106--- include/PetriEngine/options.h 2021-07-22 09:46:13 +0000
107+++ include/PetriEngine/options.h 2021-08-06 11:23:06 +0000
108@@ -85,7 +85,7 @@
109 BuchiOptimization buchiOptimization = BuchiOptimization::Low;
110 const char* ltlHeuristic = "aut";
111
112- bool replay = false;
113+ bool replay_trace = false;
114 std::string replay_file;
115
116
117
118=== modified file 'src/LTL/CMakeLists.txt'
119--- src/LTL/CMakeLists.txt 2021-04-29 13:35:13 +0000
120+++ src/LTL/CMakeLists.txt 2021-08-06 11:23:06 +0000
121@@ -7,7 +7,7 @@
122 add_subdirectory(Stubborn)
123 add_subdirectory(SuccessorGeneration)
124
125-add_library(LTL ${HEADER_FILES} LTLMain.cpp Replay.cpp)
126+add_library(LTL ${HEADER_FILES} LTLMain.cpp)
127
128 if (VERIFYPN_Static OR APPLE)
129 target_link_libraries(LTL PRIVATE LTL_algorithm LTLStubborn LTL_simplification LTLSuccessorGeneration spot bddx)
130
131=== removed file 'src/LTL/Replay.cpp'
132=== modified file 'src/PetriEngine/CMakeLists.txt'
133--- src/PetriEngine/CMakeLists.txt 2021-04-29 07:47:52 +0000
134+++ src/PetriEngine/CMakeLists.txt 2021-08-06 11:23:06 +0000
135@@ -15,6 +15,6 @@
136 ReducingSuccessorGenerator.cpp
137 STSolver.cpp
138 SuccessorGenerator.cpp
139-)
140+ TraceReplay.cpp ../../include/PetriEngine/TraceReplay.h)
141
142 target_link_libraries(PetriEngine PRIVATE Colored Structures Simplification Stubborn Reachability PQL TAR)
143
144=== added file 'src/PetriEngine/TraceReplay.cpp'
145--- src/PetriEngine/TraceReplay.cpp 1970-01-01 00:00:00 +0000
146+++ src/PetriEngine/TraceReplay.cpp 2021-08-06 11:23:06 +0000
147@@ -0,0 +1,237 @@
148+/* Copyright (C) 2021 Nikolaj J. Ulrik <nikolaj@njulrik.dk>,
149+ * Simon M. Virenfeldt <simon@simwir.dk>
150+ *
151+ * This program is free software: you can redistribute it and/or modify
152+ * it under the terms of the GNU General Public License as published by
153+ * the Free Software Foundation, either version 3 of the License, or
154+ * (at your option) any later version.
155+ *
156+ * This program is distributed in the hope that it will be useful,
157+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
158+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
159+ * GNU General Public License for more details.
160+ *
161+ * You should have received a copy of the GNU General Public License
162+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
163+ */
164+
165+#include "PetriEngine/PQL/PQL.h"
166+#include "PetriEngine/PetriNet.h"
167+#include "PetriEngine/TraceReplay.h"
168+#include "PetriEngine/Structures/State.h"
169+#include "PetriEngine/SuccessorGenerator.h"
170+#include "LTL/LTLToBuchi.h"
171+
172+#include <rapidxml.hpp>
173+#include <vector>
174+#include <iostream>
175+#include <cstring>
176+
177+
178+namespace PetriEngine {
179+
180+ TraceReplay::TraceReplay(std::istream &is, const PetriEngine::PetriNet *net, const options_t &options)
181+ :options(options) {
182+ parse(is, net);
183+ }
184+
185+ void TraceReplay::parse(std::istream &xml, const PetriNet *net) {
186+ places.clear();
187+ transitions.clear();
188+ // preallocate reverse lookup for transition and place names. Assume this is always called with the same Petri net.
189+ for (int i = 0; i < net->placeNames().size(); ++i) {
190+ places[net->placeNames()[i]] = i;
191+ }
192+ for (int i = 0; i < net->transitionNames().size(); ++i) {
193+ transitions[net->transitionNames()[i]] = i;
194+ }
195+ transitions[DEADLOCK_TRANS] = -1;
196+
197+ // TODO can also validate transition names up front.
198+ rapidxml::xml_document<> doc;
199+ std::vector<char> buffer((std::istreambuf_iterator<char>(xml)), std::istreambuf_iterator<char>());
200+ buffer.push_back('\0');
201+ doc.parse<0>(buffer.data());
202+ rapidxml::xml_node<> *root = doc.first_node();
203+
204+ if (root) {
205+ parseRoot(root);
206+ } else {
207+ std::cerr << "Error getting root node." << std::endl;
208+ assert(false);
209+ exit(1);
210+ }
211+
212+ }
213+
214+ void TraceReplay::parseRoot(const rapidxml::xml_node<> *pNode) {
215+ if (std::strcmp(pNode->name(), "trace") != 0) {
216+ std::cerr << "Error: Expected trace node. Got: " << pNode->name() << std::endl;
217+ assert(false);
218+ exit(1);
219+ }
220+ for (auto it = pNode->first_node(); it; it = it->next_sibling()) {
221+ if (std::strcmp(it->name(), "loop") == 0) loop_idx = trace.size();
222+ else if (std::strcmp(it->name(), "transition") == 0 || std::strcmp(it->name(), "deadlock") == 0) {
223+ trace.push_back(parseTransition(it));
224+ } else {
225+ std::cerr << "Error: Expected transition or loop node. Got: " << it->name() << std::endl;
226+ assert(false);
227+ exit(1);
228+ }
229+ }
230+ if (loop_idx == std::numeric_limits<size_t>::max() && options.logic == TemporalLogic::LTL) {
231+ std::cerr << "Error: Missing <loop/> statement in trace\n";
232+ exit(1);
233+ }
234+ }
235+
236+ TraceReplay::Transition TraceReplay::parseTransition(const rapidxml::xml_node<char> *pNode) {
237+ std::string id;
238+ int buchi = -1;
239+ for (auto it = pNode->first_attribute(); it; it = it->next_attribute()) {
240+ if (std::strcmp(it->name(), "id") == 0) {
241+ id = std::string(it->value());
242+ }
243+ if (std::strstr(it->name(), "buchi") != nullptr) {
244+ // buchi field is sometimes spelled slightly differently, but will always contain 'buchi'
245+ buchi = std::stoi(it->value());
246+ }
247+ }
248+ if (strcmp(pNode->name(), "deadlock") == 0) {
249+ id = DEADLOCK_TRANS;
250+ }
251+ if (id.empty()) {
252+ std::cerr << "Error: Transition has no id attribute" << std::endl;
253+ assert(false);
254+ exit(1);
255+ }
256+
257+ Transition transition(id, buchi);
258+
259+ for (auto it = pNode->first_node(); it; it = it->next_sibling()) {
260+ if (std::strcmp(it->name(), "token") != 0) {
261+ std::cerr << "Warning: Unexpected transition child. Expected: token, but got: " << it->name()
262+ << std::endl;
263+ }
264+ parseToken(it, transition.tokens);
265+ }
266+
267+ return transition;
268+ }
269+
270+ void
271+ TraceReplay::parseToken(const rapidxml::xml_node<char> *pNode,
272+ std::unordered_map<uint32_t, uint32_t> &current_marking) {
273+ for (auto it = pNode->first_attribute(); it; it = it->next_attribute()) {
274+ if (std::strcmp(it->name(), "place") == 0) {
275+ std::string val{it->value()};
276+ if (current_marking.find(places.at(val)) == current_marking.end()) {
277+ current_marking[places.at(val)] = 1;
278+ } else {
279+ ++current_marking[places.at(val)];
280+ }
281+ }
282+ }
283+ }
284+
285+ bool TraceReplay::replay(const PetriEngine::PetriNet *net, const PetriEngine::PQL::Condition_ptr &cond) {
286+ //spot::print_dot(std::cerr, buchiGenerator.aut._buchi);
287+ PetriEngine::Structures::State state;
288+ state.setMarking(net->makeInitialMarking());
289+ PetriEngine::SuccessorGenerator successorGenerator(*net);
290+
291+ std::cout << "Playing back trace. Length: " << trace.size() << std::endl;
292+ if (_play_trace(net, successorGenerator)) {
293+ std::cout << "Replay complete. No errors" << std::endl;
294+ return true;
295+ }
296+ return false;
297+ }
298+
299+ bool
300+ TraceReplay::_play_trace(const PetriEngine::PetriNet *net, PetriEngine::SuccessorGenerator &successorGenerator) {
301+ PetriEngine::Structures::State state;
302+ PetriEngine::Structures::State loopstate;
303+ bool looping = false;
304+ state.setMarking(net->makeInitialMarking());
305+ loopstate.setMarking(net->makeInitialMarking());
306+ for (int i = 0; i < trace.size(); ++i) {
307+ const Transition &transition = trace[i];
308+ // looping part should end up at the state _before_ the <loop/> tag,
309+ // hence copy state from previous iteration.
310+ if (i == loop_idx) {
311+ memcpy(loopstate.marking(), state.marking(), sizeof(uint32_t) * net->numberOfPlaces());
312+ looping = true;
313+ }
314+ successorGenerator.prepare(&state);
315+ auto it = transitions.find(transition.id);
316+ if (it == std::end(transitions)) {
317+ std::cerr << "Unrecognized transition name " << transition.id << std::endl;
318+ assert(false);
319+ exit(1);
320+ }
321+ int tid = it->second;
322+
323+ if (tid != -1) {
324+ // fire transition
325+ if (!successorGenerator.checkPreset(tid)) {
326+ std::cerr
327+ << "ERROR the provided trace cannot be replayed on the provided model. \nOffending transition: "
328+ << transition.id << " at index: " << i << "\n";
329+ return false;
330+ }
331+ successorGenerator.consumePreset(state, tid);
332+ successorGenerator.producePostset(state, tid);
333+ } else {
334+ // -1 is deadlock, assert deadlocked state.
335+ // LTL deadlocks are unambiguously in the Petri net, since Büchi deadlocks won't generate any successor in the first place.
336+ assert(i == trace.size() - 1);
337+ if (!net->deadlocked(state.marking())) {
338+ std::cerr << "ERROR: Trace claimed the net was deadlocked, but there are enabled transitions.\n";
339+ return false;
340+ }
341+ return true;
342+ }
343+
344+ if (!transition.tokens.empty()) {
345+ auto[finv, linv] = net->preset(transitions.at(transition.id));
346+ for (; finv != linv; ++finv) {
347+ if (finv->inhibitor) {
348+
349+ } else {
350+ auto it = transition.tokens.find(finv->place);
351+ if (it == std::end(transition.tokens)) {
352+ std::cerr << "ERROR: Pre-place " << net->placeNames()[finv->place] << " of transition "
353+ << transition.id << " was not mentioned in trace.\n";
354+ return false;
355+ }
356+ if (it->second != finv->tokens) {
357+ std::cerr << "ERROR: Pre-place " << net->placeNames()[finv->place] << " of transition "
358+ << transition.id << "has arc weight " << finv->tokens << " but trace consumes "
359+ << it->second << " tokens.\n";
360+ return false;
361+ }
362+ }
363+ }
364+ }
365+ }
366+
367+ bool err = false;
368+ if (looping) {
369+ for (int i = 0; i < net->numberOfPlaces(); ++i) {
370+ if (state.marking()[i] != loopstate.marking()[i]) {
371+ if (!err) {
372+ std::cerr << "ERROR end state not equal to expected loop state.\n";
373+ err = true;
374+ }
375+ std::cerr << " " << net->placeNames()[i] << ": expected" << loopstate.marking()[i] << ", actual "
376+ << state.marking()[i] << '\n';
377+ }
378+ }
379+ }
380+
381+ return !err;
382+ }
383+}
384+
385
386=== modified file 'src/VerifyPN.cpp'
387--- src/VerifyPN.cpp 2021-08-03 12:00:44 +0000
388+++ src/VerifyPN.cpp 2021-08-06 11:23:06 +0000
389@@ -86,7 +86,7 @@
390 #include "PetriEngine/PQL/Expressions.h"
391 #include "PetriEngine/Colored/ColoredPetriNetBuilder.h"
392 #include "LTL/LTL.h"
393-#include "LTL/Replay.h"
394+#include "PetriEngine/TraceReplay.h"
395 #include "LTL/LTLMain.h"
396
397 #include <atomic>
398@@ -393,8 +393,8 @@
399 }
400 ++i;
401 }
402- else if (strcmp(argv[i], "--replay") == 0) {
403- options.replay = true;
404+ else if (strcmp(argv[i], "--trace-replay") == 0) {
405+ options.replay_trace = true;
406 options.replay_file = std::string(argv[++i]);
407 }
408
409@@ -584,9 +584,12 @@
410 " simplification and Büchi construction, but gives worse\n"
411 " results since there is less opportunity for optimizations.\n"
412 " --noverify Disable verification e.g. for getting unfolded net\n"
413- " --replay <file> Replays an LTL trace output by the --trace option.\n"
414+ " --trace-replay <file> Replays a trace as output by the --trace option.\n"
415 " The trace is verified against the provided model and query.\n"
416- " --spot-optimization <1,2,3> The optimization level passed to Spot for büchi creation. 1: Low (default), 2: Medium, 3: High\n"
417+ " Mainly useful for debugging.\n"
418+ " --spot-optimization <1,2,3> The optimization level passed to Spot for Büchi automaton creation.\n"
419+ " 1: Low (default), 2: Medium, 3: High\n"
420+ " Using optimization levels above 1 may cause exponential blowups and is not recommended.\n"
421 "\n"
422 "Return Values:\n"
423 " 0 Successful, query satisfiable\n"
424@@ -625,17 +628,20 @@
425 return SuccessCode;
426 }
427 else if (strcmp(argv[i], "--ltl-heur-help") == 0) {
428- printf("Heuristics for LTL model checking are specified using the following grammar:"
429+ printf("Heuristics for LTL model checking are specified using the following grammar:\n"
430 " heurexp : {'aut' | 'automaton'}\n"
431 " | {'dist' | 'distance'}\n"
432- " | {'fc' | 'firecount' | 'fire-count'} INT\n"
433+ " | {'fc' | 'firecount' | 'fire-count'} <threshold>?\n"
434 " | '(' heurexp ')'\n"
435 " | 'sum' <weight>? heurexp <weight?> heurexp\n"
436 " | 'sum' '(' <weight>? heurexp ',' <weight>? heurexp ')'\n"
437 "Example strings:\n"
438- " - 'aut' - use the automaton heuristic for verification.\n"
439+ " - aut - use the automaton heuristic for verification.\n"
440 " - sum dist fc 1000 - use the sum of distance heuristic and fire count heuristic with threshold 1000.\n"
441+ "Weight for sum and threshold for fire count are optional and integral.\n"
442+ "Sum weights default to 1 (thus plain sum), and default fire count threshold is 5000.\n"
443 );
444+ return SuccessCode;
445 }
446 else if (options.modelfile == NULL) {
447 options.modelfile = argv[i];
448@@ -725,8 +731,8 @@
449 }
450 }
451
452- if (options.replay && options.logic != TemporalLogic::LTL) {
453- std::cerr << "Argument Error: Trace replay is only supported for LTL model checking." << std::endl;
454+ if (false && options.replay_trace && options.logic != TemporalLogic::LTL) {
455+ std::cerr << "Argument Error: Trace replay_trace is only supported for LTL model checking." << std::endl;
456 return ErrorCode;
457 }
458
459@@ -1493,24 +1499,23 @@
460
461 if(alldone) return SuccessCode;
462
463- if (options.replay) {
464+ if (options.replay_trace) {
465 if (contextAnalysis(cpnBuilder, builder, net.get(), queries) != ContinueCode) {
466 std::cerr << "Fatal error assigning indexes" << std::endl;
467 exit(1);
468 }
469 std::ifstream replay_file(options.replay_file, std::ifstream::in);
470- LTL::Replay replay{replay_file, net.get()};
471- for (int i = 0; i < results.size(); ++i) {
472- if (results[i] == ResultPrinter::LTL) {
473- replay.replay(net.get(), queries[i], options);
474- }
475+ PetriEngine::TraceReplay replay{replay_file, net.get(), options};
476+ for (size_t i=0; i < queries.size(); ++i) {
477+ if (results[i] == ResultPrinter::Unknown || results[i] == ResultPrinter::CTL || results[i] == ResultPrinter::LTL)
478+ replay.replay(net.get(), queries[i]);
479 }
480 return SuccessCode;
481 }
482
483 if(options.doVerification){
484
485- //----------------------- Verify CTL queries -----------------------//
486+ //----------------------- Verify CTL queries -----------------------//
487 std::vector<size_t> ctl_ids;
488 std::vector<size_t> ltl_ids;
489 for(size_t i = 0; i < queries.size(); ++i)
490@@ -1522,19 +1527,19 @@
491 else if (results[i] == ResultPrinter::LTL) {
492 ltl_ids.push_back(i);
493 }
494- }
495+ }
496
497- if (options.replay) {
498- if (contextAnalysis(cpnBuilder, builder, net.get(), queries) != ContinueCode) {
499- std::cerr << "Fatal error assigning indexes" << std::endl;
500- exit(1);
501- }
502- std::ifstream replay_file(options.replay_file, std::ifstream::in);
503- LTL::Replay replay{replay_file, net.get()};
504- for (int i : ltl_ids) {
505- replay.replay(net.get(), queries[i], options);
506- }
507- return SuccessCode;
508+ if (options.replay_trace) {
509+ if (contextAnalysis(cpnBuilder, builder, net.get(), queries) != ContinueCode) {
510+ std::cerr << "Fatal error assigning indexes" << std::endl;
511+ exit(1);
512+ }
513+ std::ifstream replay_file(options.replay_file, std::ifstream::in);
514+ PetriEngine::TraceReplay replay{replay_file, net.get(), options};
515+ for (int i : ltl_ids) {
516+ replay.replay(net.get(), queries[i]);
517+ }
518+ return SuccessCode;
519 }
520
521 if (!ctl_ids.empty()) {

Subscribers

People subscribed via source and target branches