diff --git a/include/TimedPetriNetEditor/PetriNet.hpp b/include/TimedPetriNetEditor/PetriNet.hpp index eebef14..bbc691b 100644 --- a/include/TimedPetriNetEditor/PetriNet.hpp +++ b/include/TimedPetriNetEditor/PetriNet.hpp @@ -821,7 +821,8 @@ bool convertTo(Net& net, TypeOfNet const type, std::string& error, std::vector @@ -94,6 +95,7 @@ class Editor: public PetriNetEditor, public Application void clearNet(); void undo(); void redo(); + void springify(); private: // Error logs @@ -195,9 +197,12 @@ class Editor: public PetriNetEditor, public Application // ******************************************************************** struct Canvas { - ImVec2 corners[2]; - ImVec2 size; - ImVec2 origin; + ImVec2 corners[2] = {{0.0f, 0.0f}, {0.0f, 0.0f}}; + ImVec2 size{800.0f, 600.0f}; // FIXME: size is undefined while not + // rendered once but when importing nets + // we need to know th windows size which + // is 0x0 + ImVec2 origin{0.0f, 0.0f}; ImVec2 scrolling{0.0f, 0.0f}; ImDrawList* draw_list; @@ -272,6 +277,9 @@ class Editor: public PetriNetEditor, public Application //! \brief Single Petri net the editor can edit. //! \fixme Manage several nets (like done with GEMMA). Net m_net; + //! Apply spring positive/negative forces on arcs/nodes to unfold imported + //! Petri nets that do not have positions on their nodes. + ForceDirected m_spring; //! \brief History of modifications of the net. History m_history; //! \brief Instance allowing to do timed simulation. diff --git a/src/Editor/DearImGui/KeyBindings.hpp b/src/Editor/DearImGui/KeyBindings.hpp index e1eac00..4cf08ca 100644 --- a/src/Editor/DearImGui/KeyBindings.hpp +++ b/src/Editor/DearImGui/KeyBindings.hpp @@ -25,6 +25,7 @@ // FIXME: The backend raylib does not support other than US keyboard meaning that // other keyboard mapping are fucked up. # define KEY_QUIT_APPLICATION ImGuiKey_Escape +# define KEY_SPRINGIFY_NET ImGuiKey_A # define KEY_RUN_SIMULATION ImGuiKey_Space # define KEY_RUN_SIMULATION_ALT ImGuiKey_R # define KEY_ROTATE_CW ImGuiKey_PageUp diff --git a/src/Net/Imports/ImportTimedEventGraph.cpp b/src/Net/Imports/ImportTimedEventGraph.cpp index c6c3716..3db3bea 100644 --- a/src/Net/Imports/ImportTimedEventGraph.cpp +++ b/src/Net/Imports/ImportTimedEventGraph.cpp @@ -74,7 +74,7 @@ std::string importFromTimedEventGraph(Net& net, std::string const& filename) size_t x = margin, y = margin; for (size_t id = 0u; id < transitions; ++id) { - net.addTransition(id, Transition::to_str(id), float(x), float(y), 0); + net.addTransition(id, Transition::to_str(id), randomInt(0,800), randomInt(0,600), 0); x += dx; if (x > w - margin) { x = margin; y += dy; } } diff --git a/src/Net/Imports/Imports.cpp b/src/Net/Imports/Imports.cpp index 6d5ae5d..d58164b 100644 --- a/src/Net/Imports/Imports.cpp +++ b/src/Net/Imports/Imports.cpp @@ -26,10 +26,10 @@ namespace tpne { std::vector const& importers() { static const std::vector s_importers = { - { "JSON", ".json", importFromJSON }, - { "Petri Net Markup Language", ".pnml", importFromPNML }, - { "Flowshop", ".flowshop", importFlowshop }, - { "Timed Event Graph", ".teg", importFromTimedEventGraph } + { "JSON", ".json", importFromJSON, false }, + { "Petri Net Markup Language", ".pnml", importFromPNML, false }, + { "Flowshop", ".flowshop", importFlowshop, false }, + { "Timed Event Graph", ".teg", importFromTimedEventGraph, true } }; return s_importers; diff --git a/src/Net/Imports/Imports.hpp b/src/Net/Imports/Imports.hpp index e10ac79..163484e 100644 --- a/src/Net/Imports/Imports.hpp +++ b/src/Net/Imports/Imports.hpp @@ -55,6 +55,8 @@ struct Importer std::string extensions; //! \brief the pointer function for importing (i.e. importFromJSON) ImportFunc importFct; + //! \brief Shall apply spring force on the net ? + bool springify; }; //! \brief Container of file formats we can import a Petri net from. diff --git a/src/Net/PetriNet.cpp b/src/Net/PetriNet.cpp index 9ee71c3..8b4ba21 100644 --- a/src/Net/PetriNet.cpp +++ b/src/Net/PetriNet.cpp @@ -755,7 +755,7 @@ std::string saveToFile(Net const& net, std::string const& filepath) } //------------------------------------------------------------------------------ -std::string loadFromFile(Net& net, std::string const& filepath) +std::string loadFromFile(Net& net, std::string const& filepath, bool& springify) { // Search the importer Importer const* importer = getImporter(extension(filepath)); @@ -771,6 +771,10 @@ std::string loadFromFile(Net& net, std::string const& filepath) { net.reset(net.type()); } + else + { + springify = importer->springify; + } // Get a name to the net if (net.name == "") diff --git a/src/Utils/ForceDirected.cpp b/src/Utils/ForceDirected.cpp new file mode 100644 index 0000000..c373c34 --- /dev/null +++ b/src/Utils/ForceDirected.cpp @@ -0,0 +1,150 @@ +/* ***************************************************************************** +** MIT License +** +** Copyright (c) 2022 Quentin Quadrat +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in all +** copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. +******************************************************************************** +*/ + +#include "Utils/ForceDirected.hpp" + +namespace tpne { + +//------------------------------------------------------------------------------ +void ForceDirected::reset(float width, float height, Net& net) +{ + std::cout << "ForceDirected::reset " << width << " x " << height << "\n"; + m_net = &net; + m_width = width; + m_height = height; + + N = net.transitions().size() + net.places().size(); + K = sqrtf(m_width * m_height / 2.0f / float(N)); + m_temperature = m_width + m_height; + m_vertices.clear(); + m_vertices.reserve(N); + + // Copy Petri nodes to Graph vertices + for (auto& it: net.transitions()) + { + m_vertices.emplace_back(it); + } + for (auto& it: net.places()) + { + m_vertices.emplace_back(it); + } + + // Lookup table: Node ID to index on the vector + std::map lookup; + for (size_t n = 0u; n < N; ++n) + { + lookup[m_vertices[n].node->key] = n; + } + + // Add edges "source node" -> "destination node". + // Since, we need undirected graph so add edges "destination node" -> "source node" + for (size_t n = 0u; n < N; ++n) + { + Vertex& v = m_vertices[n]; + v.neighbors.reserve(2u * (v.node->arcsIn.size() + v.node->arcsOut.size())); + + for (auto& it: v.node->arcsIn) + { + v.neighbors.emplace_back(m_vertices[lookup[it->from.key]].node); + v.neighbors.emplace_back(m_vertices[lookup[it->to.key]].node); + } + + for (auto& it: v.node->arcsOut) + { + v.neighbors.emplace_back(m_vertices[lookup[it->from.key]].node); + v.neighbors.emplace_back(m_vertices[lookup[it->to.key]].node); + } + } +} + +//------------------------------------------------------------------------------ +void ForceDirected::update() +{ + if (m_net == nullptr) + return ; + + if (m_temperature < 0.1f) + return ; + + step(); +} + +//------------------------------------------------------------------------------ +void ForceDirected::step() +{ + if (m_net == nullptr) + return ; + + for (auto& v: m_vertices) + { + const ImVec2 v_position(v.node->x, v.node->y); + + // Repulsive forces: nodes -- nodes + for (auto& u: m_vertices) + { + if (u.node->key == v.node->key) + continue ; + + const ImVec2 u_position(u.node->x, u.node->y); + const ImVec2 direction(v_position - u_position); + const float dist = distance(direction); + const float rf = repulsive_force(dist); + v.displacement += direction * rf / dist; + } + + // Attractive forces: edges + for (auto& u: v.neighbors) + { + if (u->key == v.node->key) + continue ; + + const ImVec2 u_position(u->x, u->y); + const ImVec2 direction(v_position - u_position); + const float dist = distance(direction); + const float af = attractive_force(dist); + v.displacement -= direction * af / dist; + } + } + + // Update position and constrain position to the window bounds + for (auto& v: m_vertices) + { + constexpr ImVec2 LAYOUT_BORDER(50.0f, 50.0f); + const float dist = distance(v.displacement); + ImVec2 v_position(v.node->x, v.node->y); + v_position += (dist > m_temperature) + ? v.displacement * m_temperature / dist + : v.displacement; + v.node->x = std::min(m_width - LAYOUT_BORDER.x, + std::max(LAYOUT_BORDER.x, v_position.x)); + v.node->y = std::min(m_height - LAYOUT_BORDER.y, + std::max(LAYOUT_BORDER.y, v_position.y)); + v.displacement = { 0.0f, 0.0f }; + } + + cooling(); +} + +} // namespace tpne \ No newline at end of file diff --git a/src/Utils/ForceDirected.hpp b/src/Utils/ForceDirected.hpp new file mode 100644 index 0000000..149d9db --- /dev/null +++ b/src/Utils/ForceDirected.hpp @@ -0,0 +1,166 @@ +/* ***************************************************************************** +** MIT License +** +** Copyright (c) 2022 Quentin Quadrat +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in all +** copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +** SOFTWARE. +******************************************************************************** +*/ + +#ifndef FORCEDIRECTEDGRAPH_HPP +# define FORCEDIRECTEDGRAPH_HPP + +# include "TimedPetriNetEditor/PetriNet.hpp" +# include "Editor/DearImGui/DearUtils.hpp" +# include +# include +# include +# include + +namespace tpne { + +// ***************************************************************************** +//! \brief Force-directed graph drawing algorithms are a class of algorithms for +//! drawing graphs in an aesthetically-pleasing way. Their purpose is to +//! position the nodes of a graph in two-dimensional or three-dimensional space +//! so that all the edges are of more or less equal length and there are as few +//! crossing edges as possible, by assigning forces among the set of edges and +//! the set of nodes, based on their relative positions, and then using these +//! forces either to simulate the motion of the edges and nodes or to minimize +//! their energy. +//! +//! Use the spring/repulsion model of Fruchterman and Reingold (1991) with: +//! - Attractive force: af(d) = d^2 / k +//! - Repulsive force: rf(d) = -k^2 / d +//! where d is distance between two vertices and the optimal distance between +//! vertices k is defined as C * sqrt(area / num_vertices) where C is a +//! parameter we can adjust. +//! +//! For more information see this video https://youtu.be/WWm-g2nLHds +//! This code source is largely inspired by: +//! https://github.com/qdHe/Parallelized-Force-directed-Graph-Drawing +// ***************************************************************************** +class ForceDirected +{ +public: + + // ************************************************************************* + //! \brief Vertex is a 2D representation of a graph node. + // ************************************************************************* + struct Vertex + { + explicit Vertex(Transition& tr) : node(&tr) {} + explicit Vertex(Place& p) : node(&p) {} + + //! \brief Place or transition. Need to access to position. + Node* node = nullptr; + //! \brief Displacement due to attractive and reuplsive forces. + ImVec2 displacement = { 0.0f, 0.0f }; + //! \brief List of neighboring nodes. + std::vector neighbors; + }; + + using Vertices = std::vector; + +public: + + //---------------------------------------------------------------------- + //! \brief Restore initial states. + //---------------------------------------------------------------------- + void reset(float width, float height, Net& net); + void reset() { m_net = nullptr; } + + //---------------------------------------------------------------------- + //! \brief Compute one step of forces if temperature is still hot else + //! do nothing. + //---------------------------------------------------------------------- + void update(); + + //---------------------------------------------------------------------- + //! \brief Const getter of vertices. + //---------------------------------------------------------------------- + inline Vertices const& vertices() const + { + return m_vertices; + } + +private: + + //---------------------------------------------------------------------- + //! \brief Do a single step for computing forces. + //---------------------------------------------------------------------- + void step(); + + //---------------------------------------------------------------------- + //! \brief Euclidian norm. + //! \param[in] p world coordinate position. + //---------------------------------------------------------------------- + inline float distance(ImVec2 const& p) const + { + return std::max(0.001f, sqrtf(p.x * p.x + p.y * p.y)); + } + + //---------------------------------------------------------------------- + //! \brief Compute repulsive force. + //! \param[in] distance. + //---------------------------------------------------------------------- + inline float repulsive_force(float const distance) const + { + return K * K / distance / float(N) / 2.0f; + } + + //---------------------------------------------------------------------- + //! \brief Compute attractive force. + //! \param[in] distance. + //---------------------------------------------------------------------- + inline float attractive_force(float const distance) const + { + return distance * distance / K / float(N); + } + + //---------------------------------------------------------------------- + //! \brief Reduce effect of forces. + //---------------------------------------------------------------------- + inline float cooling() + { + m_temperature *= 0.98f; + return m_temperature; + } + +private: + + //! \brief The directional graph to display. + Net* m_net = nullptr; + //! \brief Collection of nodes to display. + Vertices m_vertices; + //! \brief Dimension of the screen. + float m_width; + //! \brief Dimension of the screen. + float m_height; + //! \brief Reduce effect of forces. + float m_temperature; + //! \brief Force coeficient: sqrt(area / num_vertices) + float K; + //! \brief Number of vertices. + size_t N; +}; + +} // namespace tpne + +#endif \ No newline at end of file diff --git a/src/julia/Julia.cpp b/src/julia/Julia.cpp index cb24ddb..9cf107c 100644 --- a/src/julia/Julia.cpp +++ b/src/julia/Julia.cpp @@ -367,7 +367,8 @@ bool petri_load(int64_t const pn, const char* filepath) { CHECK_VALID_PETRI_HANDLE(pn, false); - std::string err = tpne::loadFromFile(*g_petri_nets[size_t(pn)], filepath); + bool springify = false; + std::string err = tpne::loadFromFile(*g_petri_nets[size_t(pn)], filepath, springify); if (err.empty()) return true; std::cerr << "Failed loading net from " << filepath << "Reason is '" diff --git a/tests/EventGraphTests.cpp b/tests/EventGraphTests.cpp index 1d5d3d0..8aacb01 100644 --- a/tests/EventGraphTests.cpp +++ b/tests/EventGraphTests.cpp @@ -35,11 +35,12 @@ TEST(TestEventGraph, TestHoward2) { std::string error; std::vector erroneous_arcs; + bool stringify; Net net(TypeOfNet::TimedPetriNet); Net canonic(TypeOfNet::TimedPetriNet); - ASSERT_STREQ(loadFromFile(net,"../data/examples/Howard2.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net,"../data/examples/Howard2.json", stringify).c_str(), ""); ASSERT_EQ(net.isEmpty(), false); ASSERT_EQ(isEventGraph(net), true); ASSERT_EQ(erroneous_arcs.empty(), true); @@ -70,8 +71,9 @@ TEST(TestEventGraph, TestHoward2) TEST(TestEventGraph, TestToCounterEquation) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json", stringify).c_str(), ""); net.generateArcsInArcsOut(); // FIXME ASSERT_EQ(isEventGraph(net), true); @@ -120,8 +122,9 @@ TEST(TestEventGraph, TestToAdjacencyMatrices) { std::vector erroneous_arcs; std::string error; Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json", stringify).c_str(), ""); ASSERT_EQ(isEventGraph(net, error, erroneous_arcs), true); ASSERT_EQ(error.empty(), true); ASSERT_EQ(erroneous_arcs.empty(), true); @@ -179,8 +182,9 @@ TEST(TestEventGraph, TestToSysLinInputOutput) { std::vector erroneous_arcs; Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/JPQ.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/JPQ.json", stringify).c_str(), ""); ASSERT_EQ(isEventGraph(net), true); SparseMatrix D; @@ -235,8 +239,9 @@ TEST(TestEventGraph, TestToSysLinNoInputNoOutput) { std::vector erroneous_arcs; std::string error; Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json", stringify).c_str(), ""); ASSERT_EQ(isEventGraph(net), true); ASSERT_EQ(isEventGraph(net, error, erroneous_arcs), true); ASSERT_EQ(error.empty(), true); @@ -326,8 +331,9 @@ TEST(TestEventGraph, TestToSysLinNoInputNoOutput) TEST(TestEventGraph, TestToDaterEquation) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json", stringify).c_str(), ""); net.generateArcsInArcsOut(); // FIXME ASSERT_EQ(isEventGraph(net), true); diff --git a/tests/HowardTests.cpp b/tests/HowardTests.cpp index 81dfe80..f3e14bd 100644 --- a/tests/HowardTests.cpp +++ b/tests/HowardTests.cpp @@ -128,6 +128,7 @@ TEST(TestHoward, TestSemiNetherlands) TEST(TestHoward, TestPetriNetSemiSimple) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; // Check dummy result is set to "invalid". CriticalCycleResult res; @@ -139,7 +140,7 @@ TEST(TestHoward, TestPetriNetSemiSimple) ASSERT_STREQ(res.message.str().c_str(), ""); // Load a net that is not event graph - ASSERT_STREQ(loadFromFile(net, "../data/examples/AppelsDurgence.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/AppelsDurgence.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::PetriNet); ASSERT_EQ(net.isEmpty(), false); res = findCriticalCycle(net); @@ -151,7 +152,7 @@ TEST(TestHoward, TestPetriNetSemiSimple) ASSERT_STREQ(res.message.str().c_str(), "The Petri net is not an event graph. Because:\n P0 has more than one output arc: T0 T4 T8\n"); // Load a net that is an event graph but that Howard does find policy (FIXME while it should) - ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedEventGraph); ASSERT_EQ(net.isEmpty(), false); res = findCriticalCycle(net); @@ -163,7 +164,7 @@ TEST(TestHoward, TestPetriNetSemiSimple) ASSERT_STREQ(res.message.str().c_str(), "No optimal policy found"); // Load a net that is an event graph - ASSERT_STREQ(loadFromFile(net,"../data/examples/Howard2.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net,"../data/examples/Howard2.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedEventGraph); ASSERT_EQ(net.isEmpty(), false); ASSERT_EQ(isEventGraph(net), true); @@ -221,8 +222,9 @@ TEST(TestHoward, TestPetriNetSemiSimple) TEST(TestHoward, TestSemiHowardExample) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/SemiHoward.teg").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/SemiHoward.teg", stringify).c_str(), ""); CriticalCycleResult res = findCriticalCycle(net); ASSERT_EQ(res.success, true); ASSERT_EQ(res.cycles, 2u); @@ -268,8 +270,9 @@ TEST(TestHoward, TestSemiHowardExample) TEST(TestHoward, TestSemiNetherlandsExample) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/SemiNetherlands.teg").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/SemiNetherlands.teg", stringify).c_str(), ""); CriticalCycleResult res = findCriticalCycle(net); ASSERT_EQ(res.success, true); ASSERT_EQ(res.cycles, 1u); diff --git a/tests/LoadJSONTests.cpp b/tests/LoadJSONTests.cpp index 59291fd..8645fb6 100644 --- a/tests/LoadJSONTests.cpp +++ b/tests/LoadJSONTests.cpp @@ -32,8 +32,9 @@ using namespace ::tpne; TEST(TestJSONLoader, DummyTransitions) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "data/DummyTransitions.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "data/DummyTransitions.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedPetriNet); ASSERT_EQ(net.m_places.size(), 1u); ASSERT_EQ(net.m_transitions.size(), 0u); @@ -44,21 +45,22 @@ TEST(TestJSONLoader, DummyTransitions) TEST(TestJSONLoader, TestLoadedInvalidNetTimedPetri) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "doesnotexist").c_str(), + ASSERT_STREQ(loadFromFile(net, "doesnotexist", stringify).c_str(), "Cannot import doesnotexist. Reason: 'unknown file extension'\n"); ASSERT_EQ(net.isEmpty(), true); - ASSERT_STREQ(loadFromFile(net, "doesnotexist.foo").c_str(), + ASSERT_STREQ(loadFromFile(net, "doesnotexist.foo", stringify).c_str(), "Cannot import doesnotexist.foo. Reason: 'unknown file extension'\n"); ASSERT_EQ(net.isEmpty(), true); - ASSERT_STREQ(loadFromFile(net, "data/BadJSON/BadType.json").c_str(), + ASSERT_STREQ(loadFromFile(net, "data/BadJSON/BadType.json", stringify).c_str(), "Failed parsing 'data/BadJSON/BadType.json'." " Reason was 'Unknown type of net: Timed event graphe'\n"); ASSERT_EQ(net.isEmpty(), true); - ASSERT_STREQ(loadFromFile(net, "data/BadJSON/NoName.json").c_str(), + ASSERT_STREQ(loadFromFile(net, "data/BadJSON/NoName.json", stringify).c_str(), "Failed parsing 'data/BadJSON/NoName.json'." " Reason was 'Missing JSON net name'\n"); ASSERT_EQ(net.isEmpty(), true); @@ -68,8 +70,9 @@ TEST(TestJSONLoader, TestLoadedInvalidNetTimedPetri) TEST(TestJSONLoader, LoadJSONfile) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "data/GRAFCET.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "data/GRAFCET.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::GRAFCET); ASSERT_EQ(net.m_places.size(), 13u); ASSERT_EQ(net.m_transitions.size(), 11u); @@ -80,8 +83,9 @@ TEST(TestJSONLoader, LoadJSONfile) TEST(TestJSONLoader, LoadAsGrafcet) { Net net(TypeOfNet::GRAFCET); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/TrafficLights.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/TrafficLights.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedPetriNet); ASSERT_EQ(net.m_places.size(), 7u); ASSERT_EQ(net.m_transitions.size(), 6u); @@ -101,7 +105,9 @@ TEST(TestJSONLoader, LoadAsGrafcet) TEST(TestJSONLoader, CheckMarks) { Net net(TypeOfNet::GRAFCET); - ASSERT_STREQ(loadFromFile(net, "../data/examples/TrafficLights.json").c_str(), ""); + bool stringify; + + ASSERT_STREQ(loadFromFile(net, "../data/examples/TrafficLights.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedPetriNet); std::vector tokens; @@ -148,13 +154,14 @@ TEST(TestJSONLoader, SaveAndLoadFile) std::string error; std::vector erroneous_arcs; Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/AppelsDurgence.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/AppelsDurgence.json", stringify).c_str(), ""); convertTo(net, TypeOfNet::PetriNet, error, erroneous_arcs); ASSERT_STREQ(error.c_str(), ""); ASSERT_EQ(erroneous_arcs.size(), 0u); ASSERT_STREQ(saveToFile(net, "/tmp/foo.json").c_str(), ""); - ASSERT_STREQ(loadFromFile(net,"/tmp/foo.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net,"/tmp/foo.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::PetriNet); ASSERT_EQ(net.m_places.size(), 13u); ASSERT_EQ(net.m_transitions.size(), 11u); @@ -168,12 +175,13 @@ TEST(TestJSONLoader, SaveAndLoadFile) TEST(TestJSONLoader, SaveAndLoadDummyNet) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; ASSERT_STREQ(saveToFile(net, "/tmp/foo.json").c_str(), ""); net.addPlace(1.0, 1.0, 2u); ASSERT_EQ(net.m_places.size(), 1u); - ASSERT_STREQ(loadFromFile(net, "/tmp/foo.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "/tmp/foo.json", stringify).c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedPetriNet); ASSERT_EQ(net.m_places.size(), 0u); ASSERT_EQ(net.m_transitions.size(), 0u); @@ -184,6 +192,8 @@ TEST(TestJSONLoader, SaveAndLoadDummyNet) TEST(TestJSONLoader, LoadUnexistingFile) { Net net(TypeOfNet::TimedPetriNet); - ASSERT_STREQ(loadFromFile(net, "foooobar.json").c_str(), + bool stringify; + + ASSERT_STREQ(loadFromFile(net, "foooobar.json", stringify).c_str(), "Failed opening 'foooobar.json'. Reason was 'No such file or directory'\n"); } diff --git a/tests/PetriNetTests.cpp b/tests/PetriNetTests.cpp index 6b7fea4..c2429f1 100644 --- a/tests/PetriNetTests.cpp +++ b/tests/PetriNetTests.cpp @@ -728,8 +728,9 @@ TEST(TestPetriNet, TestInvalidAddArc) TEST(TestPetriNet, TestLoadedNetTimedPetri) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json", stringify).c_str(), ""); ASSERT_STREQ(net.error().c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedEventGraph); ASSERT_EQ(net.isEmpty(), false); @@ -1046,8 +1047,9 @@ TEST(TestPetriNet, TestLoadedNetTimedPetri) TEST(TestPetriNet, TestLoadedNetGraphEvent) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "data/EventGraph2.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "data/EventGraph2.json", stringify).c_str(), ""); ASSERT_STREQ(net.error().c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedEventGraph); ASSERT_EQ(net.isEmpty(), false); @@ -1352,8 +1354,9 @@ TEST(TestPetriNet, TestLoadedNetGraphEvent) TEST(TestPetriNet, TestRemoveNode) { Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/Howard2.json", stringify).c_str(), ""); ASSERT_STREQ(net.error().c_str(), ""); ASSERT_EQ(net.type(), TypeOfNet::TimedEventGraph); ASSERT_EQ(net.m_next_place_id, 5u); @@ -1484,8 +1487,9 @@ TEST(TestPetriNet, TestcountBurnableTokens) std::string error; Net net(TypeOfNet::TimedPetriNet); + bool stringify; - ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json").c_str(), ""); + ASSERT_STREQ(loadFromFile(net, "../data/examples/EventGraph.json", stringify).c_str(), ""); ASSERT_STREQ(net.error().c_str(), ""); ASSERT_EQ(convertTo(net, TypeOfNet::PetriNet, error, erroneous_arcs), true);