diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..c8f91cccb63061bd23bc604dc7d7f5f15230308e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "googletest"]
+	path = test/lib/googletest
+	url = https://github.com/google/googletest.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d0e3db8bc962a17c42c37a677a3af61f0030873d..4bea03d2a6eb93524b341c658c3c4fdd6f9df24b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,4 +3,39 @@ project(GreedyAlgorithm)
 
 set(CMAKE_CXX_STANDARD 23)
 
-add_executable(GreedyAlgorithm src/main.cpp)
+add_definitions(-DDEBUG_TRANSIT_PRECOMPUTE)
+
+add_subdirectory(test)
+
+add_executable(GreedyAlgorithm
+        src/instance/graph/Node.cpp
+        src/instance/graph/Node.h
+        src/instance/graph/Edge.cpp
+        src/instance/graph/Edge.h
+        src/instance/graph/Graph.cpp
+        src/instance/graph/Graph.h
+        src/instance/requests/Request.cpp
+        src/instance/requests/Request.h
+        src/instance/Instance.cpp
+        src/instance/Instance.h
+        src/instance/graph/Line.cpp
+        src/instance/graph/Line.h
+        src/services/CSV/CSVRange.h
+        src/services/CSV/CSVIterator.h
+        test/debug.cpp
+        src/instance/graph/LineStop.cpp
+        src/instance/graph/LineStop.h
+        src/services/DatFile/DATRow.h
+        src/utils/SearchAlgorithms.h
+        src/ShortestPath/ShortestPathContainer.h
+        src/ShortestPath/TimeDependentShortestPathContainer.h
+        src/ShortestPath/TimeDependentShortestPathContainer.h
+        src/ShortestPath/ShortestPath.h
+        src/ShortestPath/Transit/TransitShortestPath.h
+        src/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
+        src/ShortestPath/Transit/TransitShortestPathPrecompute.h
+        src/ShortestPath/Transit/TransitAlgorithmState.cpp
+        src/ShortestPath/Transit/TransitAlgorithmState.h
+        src/ShortestPath/Transit/TransitShortestPathContainer.cpp
+        src/ShortestPath/Transit/TransitShortestPathContainer.h
+)
diff --git a/resources/test/instances/basic_debug_instance/PT_lines.csv b/resources/test/instances/basic_debug_instance/PT_lines.csv
new file mode 100644
index 0000000000000000000000000000000000000000..b740b9ccbdfa66754c75085d14ba65977081fcc8
--- /dev/null
+++ b/resources/test/instances/basic_debug_instance/PT_lines.csv
@@ -0,0 +1,2 @@
+#freq (min), start_time, node_1, ..., node_n
+15,340,3,4,5,6
\ No newline at end of file
diff --git a/resources/test/instances/basic_debug_instance/edges.csv b/resources/test/instances/basic_debug_instance/edges.csv
new file mode 100644
index 0000000000000000000000000000000000000000..e4106679957909fbfdbacebff93dfe8cad92dc4d
--- /dev/null
+++ b/resources/test/instances/basic_debug_instance/edges.csv
@@ -0,0 +1,8 @@
+#node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
\ No newline at end of file
diff --git a/resources/test/instances/basic_debug_instance/graph.dat b/resources/test/instances/basic_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..97764d4b1618d78d86986f38f8319ce70b1d9d9f
--- /dev/null
+++ b/resources/test/instances/basic_debug_instance/graph.dat
@@ -0,0 +1,21 @@
+#Params (seed)
+123456789
+#Nodes format : status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
+#Edges format : node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
+#PT line format : freq (min), start_time, end_time, node_1, ..., node_n
+15,340,560,3,4,5,6
\ No newline at end of file
diff --git a/resources/test/instances/basic_debug_instance/nodes.csv b/resources/test/instances/basic_debug_instance/nodes.csv
new file mode 100644
index 0000000000000000000000000000000000000000..3d925db84f69f513f8b7e99a539d9cddd9574abe
--- /dev/null
+++ b/resources/test/instances/basic_debug_instance/nodes.csv
@@ -0,0 +1,9 @@
+#status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
\ No newline at end of file
diff --git a/resources/test/instances/basic_debug_instance/params b/resources/test/instances/basic_debug_instance/params
new file mode 100644
index 0000000000000000000000000000000000000000..fac53052558e285176cbcb3357042664dc1c7bf9
--- /dev/null
+++ b/resources/test/instances/basic_debug_instance/params
@@ -0,0 +1 @@
+seed=123456789
\ No newline at end of file
diff --git a/resources/test/instances/contiguous_lines_debug_instance/graph.dat b/resources/test/instances/contiguous_lines_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..67e8ad8b90a0df3d0b1cece891aeefd48d2b677e
--- /dev/null
+++ b/resources/test/instances/contiguous_lines_debug_instance/graph.dat
@@ -0,0 +1,24 @@
+#Params (seed)
+123456789
+#Nodes format : status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
+#Edges format : node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
+#PT line format : freq (min), start_time, end_time, node_1, ..., node_n
+15,340,560,3,4,5,6
+15,340,560,6,5,4,3
+30,360,580,2,3,4,5,6,7
+30,240,580,7,6,5,4,3,2
\ No newline at end of file
diff --git a/resources/test/instances/cycling_line_debug_instance/graph.dat b/resources/test/instances/cycling_line_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..5e6abe119b0ab7da31d6ccb70cbe31df50740e35
--- /dev/null
+++ b/resources/test/instances/cycling_line_debug_instance/graph.dat
@@ -0,0 +1,21 @@
+#Params (seed)
+123456789
+#Nodes format : status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
+#Edges format : node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
+#PT line format : freq (min), start_time, end_time, node_1, ..., node_n
+30,240,580,7,6,7,5,4,3,2,7
\ No newline at end of file
diff --git a/resources/test/instances/multiple_crossing_lines_debug_instance/graph.dat b/resources/test/instances/multiple_crossing_lines_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..4cdc53d46a0ce446b94b1fc38095bc17da27a63f
--- /dev/null
+++ b/resources/test/instances/multiple_crossing_lines_debug_instance/graph.dat
@@ -0,0 +1,26 @@
+#Params (seed)
+123456789
+#Nodes format : status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
+#Edges format : node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
+#PT line format : freq (min), start_time, end_time, node_1, ..., node_n
+15,340,560,3,4,5,6
+15,340,560,6,5,4,3
+30,360,580,2,3,4,5,6,7
+30,240,580,7,6,5,4,3,2
+30,290,580,0,4,1
+30,290,580,1,4,0
\ No newline at end of file
diff --git a/resources/test/instances/multiple_cycling_lines_debug_instance/graph.dat b/resources/test/instances/multiple_cycling_lines_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..6b69917d8c38621cb98042260a55f1ca8514ebad
--- /dev/null
+++ b/resources/test/instances/multiple_cycling_lines_debug_instance/graph.dat
@@ -0,0 +1,27 @@
+#Params (seed)
+123456789
+#Nodes format : status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
+#Edges format : node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
+#PT line format : freq (min), start_time, end_time, node_1, ..., node_n
+15,340,560,3,4,5,6,3
+15,340,560,6,5,4,3,6
+30,360,580,2,3,4,5,6,7,2
+30,240,580,7,6,5,4,3,2,7
+30,240,580,7,6,7,5,4,3,2,7
+30,290,580,0,4,1,0
+30,290,580,1,4,0,1
\ No newline at end of file
diff --git a/resources/test/instances/single_node_lines_debug_instance/graph.dat b/resources/test/instances/single_node_lines_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..971382ad792e295ece30769a3cbe3a436be5c70a
--- /dev/null
+++ b/resources/test/instances/single_node_lines_debug_instance/graph.dat
@@ -0,0 +1,23 @@
+#Params (seed)
+123456789
+#Nodes format : status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
+#Edges format : node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
+#PT line format : freq (min), start_time, end_time, node_1, ..., node_n
+15,340,560,3,3,3,3,3,3,3,3,3
+15,340,560,7,7,7,7,7,7,7,7
+30,360,580,2,3,4,5,6,7
\ No newline at end of file
diff --git a/resources/test/instances/two_lines_debug_instance/graph.dat b/resources/test/instances/two_lines_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..813e885cc9def2e29ae9fb6a1c6f5829c8b8e67b
--- /dev/null
+++ b/resources/test/instances/two_lines_debug_instance/graph.dat
@@ -0,0 +1,22 @@
+#Params (seed)
+123456789
+#Nodes format : status,x,y
+,0,7
+,1,6
+,2,5
+,3,4
+,4,3
+,5,2
+,6,1
+,7,0
+#Edges format : node_in,node_out,length
+0,1,1
+1,2,1
+2,3,2
+2,4,4
+4,5,2
+5,6,3
+6,7,2
+#PT line format : freq (min), start_time, end_time, node_1, ..., node_n
+15,340,560,3,4,5,6
+30,360,580,0,1,2,3,7
\ No newline at end of file
diff --git a/src/ShortestPath/ShortestPath.h b/src/ShortestPath/ShortestPath.h
new file mode 100644
index 0000000000000000000000000000000000000000..8ad32901153e96d46f5414919f1c3313bdcf3462
--- /dev/null
+++ b/src/ShortestPath/ShortestPath.h
@@ -0,0 +1,37 @@
+//
+// Created by rbernard on 20/02/2024.
+//
+
+#ifndef GREEDYALGORITHM_SHORTESTPATH_H
+#define GREEDYALGORITHM_SHORTESTPATH_H
+
+#include <vector>
+
+template <typename KeyPoint>
+class ShortestPath {
+protected:
+    std::vector<KeyPoint> _keyPoints;
+
+
+public:
+    explicit ShortestPath() = default;
+
+    [[nodiscard]] const std::vector<KeyPoint> &getKeyPoints() const {
+        return _keyPoints;
+    }
+
+    void replaceKeyPoint(int keyPointIndex, KeyPoint& value) {
+        _keyPoints.at(keyPointIndex) = value;
+    }
+
+    /**
+     * Adds reference to a key point to the shortest path (e.g : a node index, a Line Stop)
+     * @param keyPoint The key point to add at the end of the key points vector
+     */
+    void addKeyPoint(KeyPoint& keyPoint) {
+        _keyPoints.emplace_back(keyPoint);
+    }
+};
+
+
+#endif //GREEDYALGORITHM_SHORTESTPATH_H
diff --git a/src/ShortestPath/ShortestPathContainer.h b/src/ShortestPath/ShortestPathContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..3a3b9a54f06761b4bd642507682b95737c418d3d
--- /dev/null
+++ b/src/ShortestPath/ShortestPathContainer.h
@@ -0,0 +1,20 @@
+//
+// Created by rbernard on 20/02/2024.
+//
+
+#ifndef GREEDYALGORITHM_SHORTESTPATHCONTAINER_H
+#define GREEDYALGORITHM_SHORTESTPATHCONTAINER_H
+
+#include "ShortestPath.h"
+
+/**
+ * Abstract class defining the search API for x to y shortest path containers
+ */
+template <typename KeyPoint>
+class ShortestPathContainer {
+public:
+    virtual ShortestPath<KeyPoint> findShortestPath(int startNode, int destinationNode, int startTimestep) = 0;
+};
+
+
+#endif //GREEDYALGORITHM_SHORTESTPATHCONTAINER_H
diff --git a/src/ShortestPath/TimeDependentShortestPathContainer.h b/src/ShortestPath/TimeDependentShortestPathContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..d5eec8acb4ca0c2d2ec8933e00a91ae4d4bd634e
--- /dev/null
+++ b/src/ShortestPath/TimeDependentShortestPathContainer.h
@@ -0,0 +1,20 @@
+//
+// Created by rbernard on 20/02/2024.
+//
+
+#ifndef GREEDYALGORITHM_TIMEDEPENDENTSHORTESTPATHCONTAINER_H
+#define GREEDYALGORITHM_TIMEDEPENDENTSHORTESTPATHCONTAINER_H
+
+#include <vector>
+#include "ShortestPathContainer.h"
+
+template <typename KeyPoint>
+class TimeDependentShortestPathContainer {
+public:
+    virtual ShortestPath<KeyPoint> findShortestPath(int startNode, int destinationNode, int startTimestep) = 0;
+    virtual std::vector<ShortestPath<KeyPoint>> findDestinationsFrom(int startNode, int startTimestep) = 0;
+    virtual std::vector<ShortestPath<KeyPoint>> findDepartureListToArriveAtWindow(int destinationNode, int minArrivalTimestep, int maxArrivalTimestep) = 0;
+};
+
+
+#endif //GREEDYALGORITHM_TIMEDEPENDENTSHORTESTPATHCONTAINER_H
diff --git a/src/ShortestPath/Transit/TransitAlgorithmState.cpp b/src/ShortestPath/Transit/TransitAlgorithmState.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e61a3a68e934230baceee999a25030b53126a420
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitAlgorithmState.cpp
@@ -0,0 +1,5 @@
+//
+// Created by romain on 21/02/24.
+//
+
+#include "TransitAlgorithmState.h"
diff --git a/src/ShortestPath/Transit/TransitAlgorithmState.h b/src/ShortestPath/Transit/TransitAlgorithmState.h
new file mode 100644
index 0000000000000000000000000000000000000000..d069858f14e66d27b6590f9c72a434d338c81393
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitAlgorithmState.h
@@ -0,0 +1,224 @@
+//
+// Created by romain on 21/02/24.
+//
+
+#ifndef GREEDYALGORITHM_TRANSITALGORITHMSTATE_H
+#define GREEDYALGORITHM_TRANSITALGORITHMSTATE_H
+
+
+#include <array>
+#include <cstdint>
+#include "../../instance/graph/LineStop.h"
+
+class TransitAlgorithmState {
+private:
+    int _nodeIndex;
+    int _instant;
+    int _passageIndex;
+    std::vector<LineStop> _connections;
+    int _precedingNodeIndex;
+
+public:
+    TransitAlgorithmState(int currentNode, int currentInstant, int currentPassageIndex, int precedingNodeIndex) {
+        _nodeIndex = currentNode;
+        _instant = currentInstant;
+        _passageIndex = currentPassageIndex;
+        _precedingNodeIndex = precedingNodeIndex;
+        _connections.reserve(2); //TODO : replace constant max amount of connexions with a global parameter
+    }
+
+    TransitAlgorithmState(TransitAlgorithmState& baseState) {
+        _nodeIndex = baseState.getNodeIndex();
+        _instant = baseState.getInstant();
+        _passageIndex = baseState.getPassageIndex();
+        _precedingNodeIndex = baseState.getPrecedingNodeIndex();
+
+        //Copy old connections
+        _connections.clear();
+        _connections.reserve(2);
+        for(auto& lineStop : baseState.getConnections()) {
+            _connections.emplace_back(lineStop);
+        }
+    }
+
+    TransitAlgorithmState(TransitAlgorithmState&& baseStatePointer) = default;
+
+    TransitAlgorithmState(TransitAlgorithmState& baseState, const LineStop& newConnection) {
+        _nodeIndex = baseState.getNodeIndex();
+        _instant = baseState.getInstant();
+        _passageIndex = baseState.getPassageIndex();
+        _precedingNodeIndex = baseState.getPrecedingNodeIndex();
+
+        //Copy old connections
+        _connections.clear();
+        _connections.reserve(2);
+        for(auto& lineStop : baseState.getConnections()) {
+            _connections.emplace_back(lineStop);
+        }
+
+        addNewConnection(newConnection);
+    }
+
+    explicit TransitAlgorithmState(int nodeIndex) {
+        _nodeIndex = nodeIndex;
+        _instant = INT16_MAX;
+        _passageIndex = -1;
+        _precedingNodeIndex = -1;
+        _connections.reserve(2);
+    }
+
+    explicit TransitAlgorithmState() {
+        _nodeIndex = -1;
+        _instant = INT16_MAX;
+        _passageIndex = -1;
+        _precedingNodeIndex = -1;
+        _connections.reserve(2);
+    }
+
+    [[nodiscard]] int getNodeIndex() const {
+        return _nodeIndex;
+    }
+
+    [[nodiscard]] int getInstant() const {
+        return _instant;
+    }
+
+    [[nodiscard]] int getPassageIndex() const {
+        return _passageIndex;
+    }
+
+    [[nodiscard]] const std::vector<LineStop> &getConnections() const {
+        return _connections;
+    }
+
+    [[nodiscard]] bool isEmpty() const {
+        return _connections.empty();
+    }
+
+    [[nodiscard]] bool canAddConnection() const {
+        return _connections.size() < 2;
+    }
+
+    [[nodiscard]] size_t getNbConnections() const {
+        return _connections.size();
+    }
+
+    [[nodiscard]] LineStop getLastConnectionLineStop() const {
+        return _connections.back();
+    }
+
+    [[nodiscard]] Line getLastConnectionLine() const {
+        return _connections.back().getLineRef();
+    }
+
+    [[nodiscard]] int getPrecedingNodeIndex() const {
+        return _precedingNodeIndex;
+    }
+
+    void setPrecedingNodeIndex(int nodeIndex) {
+        _precedingNodeIndex = nodeIndex;
+    }
+
+    [[nodiscard]] int getNextNodeIndex() const {
+        return _connections.back().getNextNodeIndex();
+    }
+
+    void setNodeIndex(int nodeIndex) {
+        _nodeIndex = nodeIndex;
+    }
+
+    void setInstant(int instant) {
+        _instant = instant;
+    }
+
+    void setPassageIndex(int passageIndex) {
+        _passageIndex = passageIndex;
+    }
+
+    /**
+     *
+     * @param connection
+     * @return
+     */
+    bool addNewConnection(const LineStop& connection)
+    {
+        if(_connections.size() < _connections.max_size()) {
+            _connections.emplace_back(connection);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    bool canAddNewConnection() {return _connections.size() < _connections.max_size(); }
+
+    /**
+     * Strict dominance between two transit shortest path states happens if state *this* has :
+     * 1) lower amount of connections and same arrival time OR 2) lower arrival time than the state rhs
+     * @param rhs
+     * @return
+     */
+    [[nodiscard]] bool strictlyDominates(const TransitAlgorithmState& rhs) const {
+        return this->getNodeIndex() == rhs.getNodeIndex()
+        && this->getLastConnectionLine().getLineId() == rhs.getLastConnectionLine().getLineId()
+//        /*TODO : check */ && (this->getLastConnectionLine() == rhs.getLastConnectionLine() || this->getNbConnections() == 2) /***/
+        && ((this->getInstant() < rhs.getInstant() && this->getConnections().size() <= rhs.getConnections().size())
+        || (this->getInstant() == rhs.getInstant() && this->getConnections().size() < rhs.getConnections().size()));
+    }
+
+    /**
+     * States are ordered by their best arrival time first, and if equal, by the amount of connections to decide which state to extend first
+     * @param rhs
+     * @return
+     */
+    bool operator<(const TransitAlgorithmState& rhs) const {
+        return this->_nodeIndex == rhs.getNodeIndex() //same current node
+                && (this->getInstant() < rhs.getInstant() //strictly better time
+                || (this->getInstant() == rhs.getInstant()
+                    && this->getConnections().size() < rhs.getConnections().size())); //same arrival time and strictly better connections
+    }
+
+    bool operator>(const TransitAlgorithmState& rhs) const {
+        return this->_nodeIndex == rhs.getNodeIndex() //same current node
+                && (this->getInstant() > rhs.getInstant()
+                || (this->getInstant() == rhs.getInstant()
+                    && this->getConnections().size() > rhs.getConnections().size()));
+    }
+
+    bool operator==(const TransitAlgorithmState& rhs) const {
+        return this->_nodeIndex == rhs.getNodeIndex() //same current node
+                && this->getInstant() == rhs.getInstant()
+                && this->getConnections().size() == rhs.getConnections().size();
+    }
+
+    bool operator!=(const TransitAlgorithmState& rhs) const {
+        return this->_nodeIndex != rhs.getNodeIndex() //same current node
+                || this->getInstant() != rhs.getInstant()
+                || this->getConnections().size() != rhs.getConnections().size();
+    }
+
+    TransitAlgorithmState& operator=(const TransitAlgorithmState& baseState) = default;
+
+    [[nodiscard]] std::string toString() const {
+        std::string res = "Node: " + std::to_string(_nodeIndex) + ", Instant: " + std::to_string(_instant);
+
+        //Add line names in order if needed
+        if(!_connections.empty()) {
+            res += ", Connections: ";
+            if(_connections.size() > 1) {
+                for(int i = 0; i < _connections.size() - 1; ++i) {
+                    res += _connections.at(i).getLineRef().getLineId() + " -> ";
+                }
+            }
+
+            res += _connections.at(_connections.size() - 1).getLineRef().getLineId();
+
+        }
+
+        return res;
+    }
+
+};
+
+
+#endif //GREEDYALGORITHM_TRANSITALGORITHMSTATE_H
diff --git a/src/ShortestPath/Transit/TransitShortestPath.h b/src/ShortestPath/Transit/TransitShortestPath.h
new file mode 100644
index 0000000000000000000000000000000000000000..c2014d13c908b8defe30222a45c3fcd9d905519d
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitShortestPath.h
@@ -0,0 +1,63 @@
+//
+// Created by rbernard on 20/02/2024.
+//
+
+#ifndef GREEDYALGORITHM_TRANSITSHORTESTPATH_H
+#define GREEDYALGORITHM_TRANSITSHORTESTPATH_H
+
+#include "../ShortestPath.h"
+#include "../../instance/graph/LineStop.h"
+#include "TransitAlgorithmState.h"
+
+class TransitShortestPath : public ShortestPath<LineStop> {
+private:
+    int _arrivalTime;
+public:
+
+    explicit TransitShortestPath(const TransitAlgorithmState& state) {
+        _arrivalTime = state.getInstant();
+        std::move(state.getConnections().begin(), state.getConnections().end() - 1,_keyPoints.begin());
+    }
+
+    /**
+     * Strict dominance between two transit shortest path
+     * @param rhs
+     * @return
+     */
+    [[nodiscard]] bool strictlyDominates(const TransitShortestPath& rhs) const {
+        return this->getKeyPoints().size() <= rhs.getKeyPoints().size()
+               && this->getArrivalTime() <= rhs.getArrivalTime();
+    }
+
+    bool operator<(const TransitShortestPath& rhs) const {
+        return this->getArrivalTime() < rhs.getArrivalTime() ||
+               (this->getArrivalTime() == rhs.getArrivalTime() && this->getKeyPoints().size() < rhs.getKeyPoints().size());
+    }
+
+    bool operator>(const TransitShortestPath& rhs) const {
+        return this->getArrivalTime() > rhs.getArrivalTime() ||
+               (this->getArrivalTime() == rhs.getArrivalTime() && this->getKeyPoints().size() > rhs.getKeyPoints().size());
+    }
+
+    [[nodiscard]] int getArrivalTime() const { return _arrivalTime; }
+
+
+//    FIXME: check if I can properly remove this or if shortest path comparisons may be useful
+//    [[nodiscard]] bool strictlyDominates(const TransitAlgorithmState& rhs) const {
+//        return this->getKeyPoints().size() <= rhs.getConnections().size()
+//               && this->getDuration() <= rhs.getInstant();
+//    }
+//
+//    bool operator<(const TransitAlgorithmState& rhs) const {
+//        return this->getDuration() < rhs.getInstant() ||
+//               (this->getDuration() == rhs.getInstant() && this->getKeyPoints().size() < rhs.getConnections().size());
+//    }
+//
+//    bool operator>(const TransitAlgorithmState& rhs) const {
+//        return this->getDuration() > rhs.getInstant() ||
+//               (this->getDuration() == rhs.getInstant() && this->getKeyPoints().size() > rhs.getConnections().size());
+//    }
+};
+
+
+#endif //GREEDYALGORITHM_TRANSITSHORTESTPATH_H
diff --git a/src/ShortestPath/Transit/TransitShortestPathContainer.cpp b/src/ShortestPath/Transit/TransitShortestPathContainer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bee8ce44b11ed7015d414e6a774d9e77802dac4f
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitShortestPathContainer.cpp
@@ -0,0 +1,39 @@
+//
+// Created by romain on 13/03/24.
+//
+
+#include "TransitShortestPathContainer.h"
+#include "TransitStateContainer.h"
+
+void TransitShortestPathContainer::addShortestPathCollection(int startNodeIndex,
+                                                             const std::pair<int, std::vector<TransitShortestPath>>& shortestPathList) {
+    container.at(startNodeIndex).emplace_back(shortestPathList);
+}
+
+void TransitShortestPathContainer::addShortestPathCollection(int startNodeIndex, int startingInstant, int graphSize,
+                                                             const TransitStateContainer& algorithmResultStates) {
+    std::vector<TransitShortestPath> shortestPathList;
+    shortestPathList.reserve(graphSize);
+
+    //Convert states to shortest paths and add to collection
+    for(int i = 0; i < graphSize; ++i) {
+        shortestPathList.emplace_back(algorithmResultStates.getBestSolution(i));
+    }
+
+    //Add the (startingInstant, pathVector) pair at the appropriate node index
+    container.at(startNodeIndex).emplace_back(startingInstant, shortestPathList);
+}
+
+std::vector<std::pair<int, std::vector<TransitShortestPath>>>::iterator
+TransitShortestPathContainer::getShortestPathsFromTime(int startNodeIndex, int earliestStartInstant) {
+    const auto& iterator = std::lower_bound(container.at(startNodeIndex).begin(), container.at(startNodeIndex).end(),
+                                            std::pair<int, std::vector<TransitShortestPath>>(earliestStartInstant, {}));
+
+    return iterator;
+}
+
+std::pair<int, TransitShortestPath>
+TransitShortestPathContainer::getShortestPathToYFromTime(int startNodeIndex, int earliestStartInstant, int goalNode) {
+    const auto& shortestPathsIterator = getShortestPathsFromTime(startNodeIndex, earliestStartInstant);
+    return std::pair(shortestPathsIterator->first, shortestPathsIterator->second.at(goalNode));
+}
diff --git a/src/ShortestPath/Transit/TransitShortestPathContainer.h b/src/ShortestPath/Transit/TransitShortestPathContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..55166e4c813dee05d7e72396e07c24d81092369f
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitShortestPathContainer.h
@@ -0,0 +1,28 @@
+//
+// Created by romain on 13/03/24.
+//
+
+#ifndef GREEDYALGORITHM_TRANSITSHORTESTPATHCONTAINER_H
+#define GREEDYALGORITHM_TRANSITSHORTESTPATHCONTAINER_H
+
+
+#include <vector>
+#include "TransitShortestPath.h"
+#include "TransitStateContainer.h"
+
+class TransitShortestPathContainer {
+private:
+    std::vector<std::vector<std::pair<int, std::vector<TransitShortestPath>>>> container; //NodeVector< PairVector<Pair<Instant, NodeVector<ShortestPath> >> >
+
+public:
+    explicit TransitShortestPathContainer(int size) { container.resize(size); }
+    explicit TransitShortestPathContainer(size_t size) { container.resize(size); }
+    void addShortestPathCollection(int startNodeIndex, const std::pair<int, std::vector<TransitShortestPath>>& shortestPathList);
+    void addShortestPathCollection(int startNodeIndex, int startingInstant, int graphSize, const TransitStateContainer& algorithmResultStates);
+    std::vector<std::pair<int, std::vector<TransitShortestPath>>>::iterator getShortestPathsFromTime(int startNodeIndex, int earliestStartInstant);
+    std::pair<int, TransitShortestPath> getShortestPathToYFromTime(int startNodeIndex, int earliestStartInstant, int goalNode);
+
+};
+
+
+#endif //GREEDYALGORITHM_TRANSITSHORTESTPATHCONTAINER_H
diff --git a/src/ShortestPath/Transit/TransitShortestPathPrecompute.cpp b/src/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..20dbf836d3e8296f9cbe7491e534cee4ae3c4d7c
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
@@ -0,0 +1,87 @@
+//
+// Created by rbernard on 20/02/2024.
+//
+
+#include "TransitShortestPathPrecompute.h"
+#include "TransitStateContainer.h"
+#include <queue>
+
+#ifdef DEBUG_TRANSIT_PRECOMPUTE
+#include <iostream>
+#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
+#else
+#define DEBUG_MSG(str) do { } while ( false )
+#endif
+
+//TODO:
+//  Tests :
+//      - priority queue order
+//      - Reference value still valid after popping (otherwise, pop at the end, before adding new state)
+TransitStateContainer TransitShortestPathPrecompute::executeAlgorithm(const Graph& graph, int nodeIndex, int instant) {
+    //Init container, priority queue and state variables
+    TransitStateContainer solutionsContainer{graph.getNbNodes()};
+    std::priority_queue<TransitAlgorithmState> statePriorityQueue;
+    statePriorityQueue.emplace(nodeIndex, instant,0, nodeIndex);
+
+    TransitAlgorithmState currentState;
+    while(!statePriorityQueue.empty())
+    {
+        //Extract head from our state priority queue
+        currentState = statePriorityQueue.top();
+        statePriorityQueue.pop();
+        if(!solutionsContainer.strictlyDominates(currentState)) {
+            DEBUG_MSG("\n\nComparing state " + currentState.toString() + " and " + solutionsContainer.getBestSolution(currentState.getNodeIndex(), currentState.getNbConnections()).toString());
+            for (auto& lineStop : graph.getPTLinesSet(currentState.getNodeIndex()))
+            {
+                int nextNode = lineStop.getNextNodeIndex();
+                //If there is a proper next node and if it's not the same as our last used line stop predecessor
+                if(nextNode != -1 && (currentState.isEmpty() || nextNode != currentState.getPrecedingNodeIndex())) {
+                    DEBUG_MSG("Extension from line " + lineStop.getLineRef().getLineId() + " towards node " + std::to_string(nextNode));
+                    TransitAlgorithmState newState; //define variable before conditionals
+                    int nextPassageIndex = currentState.getPassageIndex(); //default value if we stay on the same line and no turn back happens
+                    if(currentState.isEmpty() || currentState.getLastConnectionLine() != lineStop.getLineRef()) // if new line is different than current line
+                    {
+                        if(currentState.canAddConnection()) {
+                            nextPassageIndex = lineStop.findNextScheduledPassage(lineStop.getStopIndex(), currentState.getInstant());
+                            if (nextPassageIndex == lineStop.getLineRef().scheduleSize()) {
+                                newState.setNodeIndex(-1);
+                            } else {
+                                newState = TransitAlgorithmState(currentState, lineStop);
+                                newState.setNodeIndex(nextNode);
+                                newState.setPassageIndex(nextPassageIndex); //get next passage for new line
+                                newState.setInstant(lineStop.getInstant(lineStop.getStopIndex() + 1,nextPassageIndex)); //replace time with arrival time on next node
+                                newState.setPrecedingNodeIndex(currentState.getNodeIndex());
+                            }
+                        }
+                    } else {
+                        //Check for a cycle, and in this case, look for a new passage index
+                        if(lineStop.getInstant(lineStop.getStopIndex() + 1, currentState.getPassageIndex()) < currentState.getInstant()) {
+                            newState.setNodeIndex(-1);
+                        } else {
+                            newState = TransitAlgorithmState(currentState);
+                            newState.setNodeIndex(nextNode);
+                            newState.setPassageIndex(currentState.getPassageIndex()); //get next passage for new line
+                            newState.setInstant(lineStop.getInstant(lineStop.getStopIndex() + 1, nextPassageIndex)); //replace time with
+                            newState.setPrecedingNodeIndex(currentState.getNodeIndex());
+                        }
+                    }
+
+                    DEBUG_MSG("Created new state " + newState.toString());
+
+                    //Add new state to the solution container and the priority queue if it's not strictly dominated by an existing solution
+                    if(newState.getNodeIndex() != -1 && !solutionsContainer.strictlyDominates(newState)) {
+                        DEBUG_MSG("Candidate state " + newState.toString() + " added to priority queue\n");
+                        solutionsContainer.replaceBestSolutionIfNecessary(nextNode, newState);
+                        statePriorityQueue.emplace(newState);
+                    }
+                }
+            }
+            DEBUG_MSG("Done extending state " + currentState.toString());
+        }
+    }
+
+    //TODO: maybe finalise our container
+    //  Solution formatting should be done elsewhere for isolation purposes
+
+    return solutionsContainer;
+}
diff --git a/src/ShortestPath/Transit/TransitShortestPathPrecompute.h b/src/ShortestPath/Transit/TransitShortestPathPrecompute.h
new file mode 100644
index 0000000000000000000000000000000000000000..229cd0c491bce4391041046c8e798f9d19dbbfa3
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitShortestPathPrecompute.h
@@ -0,0 +1,20 @@
+//
+// Created by rbernard on 20/02/2024.
+//
+
+#ifndef GREEDYALGORITHM_TRANSITSHORTESTPATHPRECOMPUTE_H
+#define GREEDYALGORITHM_TRANSITSHORTESTPATHPRECOMPUTE_H
+
+#include "../TimeDependentShortestPathContainer.h"
+#include "TransitShortestPath.h"
+#include "../../instance/graph/Graph.h"
+#include "TransitStateContainer.h"
+
+class TransitShortestPathPrecompute {
+public:
+    static ShortestPathContainer<TransitShortestPath> formatResults(TimeDependentShortestPathContainer<TransitShortestPath> container);
+    static TransitStateContainer executeAlgorithm(const Graph& graph, int nodeIndex, int instant);
+};
+
+
+#endif //GREEDYALGORITHM_TRANSITSHORTESTPATHPRECOMPUTE_H
diff --git a/src/ShortestPath/Transit/TransitStateContainer.h b/src/ShortestPath/Transit/TransitStateContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..fbb3ea4a263190fcc4a30b8e92fc11da70a7bab5
--- /dev/null
+++ b/src/ShortestPath/Transit/TransitStateContainer.h
@@ -0,0 +1,108 @@
+//
+// Created by rbernard on 20/02/2024.
+//
+
+#ifndef GREEDYALGORITHM_ALGORITHMITERATIONSOLUTIONCONTAINER_H
+#define GREEDYALGORITHM_ALGORITHMITERATIONSOLUTIONCONTAINER_H
+
+#include <vector>
+#include "TransitShortestPath.h"
+
+#ifdef DEBUG_TRANSIT_PRECOMPUTE
+#include <iostream>
+#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
+#else
+#define DEBUG_MSG(str) do { } while ( false )
+#endif
+
+class TransitStateContainer {
+private:
+    //    int x,delta;
+    std::vector<std::vector<TransitAlgorithmState>>  solutionVector;
+
+public:
+    explicit TransitStateContainer(int size)
+    {
+        solutionVector.resize(size);
+        for(size_t i = 0; i < size; ++i) {
+            solutionVector.at(i).emplace_back();
+            solutionVector.at(i).emplace_back();
+        }
+    }
+    explicit TransitStateContainer(size_t size)
+    {
+        //Reserve space for all nodes and add empty solutions to reserve memory
+        solutionVector.resize(size);
+        for(size_t i = 0; i < size; ++i) {
+            solutionVector.at(i).emplace_back();
+            solutionVector.at(i).emplace_back();
+        }
+    }
+    /**
+     * Returns the current best solution for the given node index
+     * @param nodeIndex
+     * @return The first solution of potentially two saved in this array
+     */
+    [[nodiscard]] const TransitAlgorithmState& getBestSolution(int nodeIndex) const{
+        if(solutionVector.at(1) < solutionVector.at(0))
+            return solutionVector.at(nodeIndex).at(1);
+        else
+            return solutionVector.at(nodeIndex).at(0);
+    }
+
+    TransitAlgorithmState& getBestSolution(int nodeIndex, int nbConnections){ return solutionVector.at(nodeIndex).at(nbConnections == 0 ? 0 : nbConnections - 1);}
+    std::vector<TransitAlgorithmState>& getSolutions(int nodeIndex){ return solutionVector.at(nodeIndex);}
+    std::vector<std::vector<TransitAlgorithmState>>& getSolutionsVector(){ return solutionVector;}
+
+    bool strictlyDominates(const TransitAlgorithmState& state){
+        if(state.getNodeIndex() != -1)
+            return getBestSolution(state.getNodeIndex(), state.getNbConnections()).strictlyDominates(state);
+        else
+            return true;
+    }
+
+
+    /**
+     * Resizes solution vector and initializes state vectors to be able to add our solutions during algorithm execution
+     * @param nbNodes How many nodes there are in the full graph
+     */
+    void resizeSolutionsVector(int nbNodes){ solutionVector.resize(nbNodes);}
+
+
+    void replaceBestSolutionIfNecessary(int nodeIndex, const TransitAlgorithmState& newState)
+    {
+        if(newState.getNbConnections() > 0) {
+            TransitAlgorithmState& currentBest = solutionVector.at(nodeIndex).at(newState.getNbConnections() - 1);
+            if(currentBest.getInstant() > newState.getInstant()) {
+                DEBUG_MSG("Candidate state " + newState.toString() + " is the new fastest solution");
+                solutionVector.at(nodeIndex).at(newState.getNbConnections() - 1) = newState;
+            }
+        }
+    }
+
+    /**
+     * Compares the states available to get to a given node index and returns the best by comparing their time of arrival.
+     * If equivalent solutions wrt time exist, the one with the lowest amount of connections required will be returned
+     * @param nodeIndex The node we try to get to
+     * @return
+     */
+    TransitAlgorithmState getBestSolution(int nodeIndex) {
+        TransitAlgorithmState currentBestSol = solutionVector.at(nodeIndex).at(0);
+        for(size_t i = 1; i < solutionVector.at(nodeIndex).size(); ++i) {
+            if(solutionVector.at(nodeIndex).at(i).getInstant() < currentBestSol.getInstant()) {
+                currentBestSol = solutionVector.at(nodeIndex).at(i);
+            }
+        }
+
+        return currentBestSol;
+    }
+
+    void pushEmptyState(int nodeIndex)
+    {
+        TransitAlgorithmState newState = TransitAlgorithmState(nodeIndex);
+        solutionVector.at(nodeIndex).emplace_back(newState);
+    }
+};
+
+
+#endif //GREEDYALGORITHM_ALGORITHMITERATIONSOLUTIONCONTAINER_H
diff --git a/src/instance/Instance.cpp b/src/instance/Instance.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f650d9fefccee90a97d6a78e7600499338c6a954
--- /dev/null
+++ b/src/instance/Instance.cpp
@@ -0,0 +1,7 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#include "Instance.h"
+#include "graph/Graph.h"
+#include "requests/Request.h"
\ No newline at end of file
diff --git a/src/instance/Instance.h b/src/instance/Instance.h
new file mode 100644
index 0000000000000000000000000000000000000000..ebf12ec954cbb66ae326dd7be940b108367a2547
--- /dev/null
+++ b/src/instance/Instance.h
@@ -0,0 +1,22 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#ifndef GREEDYALGORITHM_INSTANCE_H
+#define GREEDYALGORITHM_INSTANCE_H
+#include <vector>
+#include "requests/Request.h"
+#include "graph/Graph.h"
+
+class Instance {
+private:
+    std::vector<Request> requests;
+    Graph graph;
+
+public:
+    [[nodiscard]] Graph const & getGraph() const { return graph;}
+    [[nodiscard]] std::vector<Request> const & getRequests() const { return requests;}
+};
+
+
+#endif //GREEDYALGORITHM_INSTANCE_H
diff --git a/src/instance/graph/Edge.cpp b/src/instance/graph/Edge.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5c8a04369f29bbaea58962182c0b2187175ee704
--- /dev/null
+++ b/src/instance/graph/Edge.cpp
@@ -0,0 +1,35 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#include "Edge.h"
+
+int Edge::getNodeStart() const {
+    return _start;
+}
+
+void Edge::setNodeStart(int start) {
+    Edge::_start = start;
+}
+
+int Edge::getNodeEnd() const {
+    return _end;
+}
+
+void Edge::setNodeEnd(int end) {
+    Edge::_end = end;
+}
+
+double Edge::getLength() const {
+    return _length;
+}
+
+void Edge::setLength(double d) {
+    Edge::_length = d;
+}
+
+Edge::Edge(int start, int end, double length) {
+    _start = start;
+    _end = end;
+    _length = length;
+}
diff --git a/src/instance/graph/Edge.h b/src/instance/graph/Edge.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7126a08094e1301ded8ecf0590db606b1949214
--- /dev/null
+++ b/src/instance/graph/Edge.h
@@ -0,0 +1,34 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#ifndef GREEDYALGORITHM_EDGE_H
+#define GREEDYALGORITHM_EDGE_H
+
+
+#include "Node.h"
+
+class Edge {
+private:
+    int _start;
+    int _end;
+    double _length;
+
+public:
+    int getNodeStart() const;
+
+    void setNodeStart(int start);
+
+    int getNodeEnd() const;
+
+    void setNodeEnd(int end);
+
+    double getLength() const;
+
+    void setLength(double d);
+
+    Edge(int start, int end, double length);
+};
+
+
+#endif //GREEDYALGORITHM_EDGE_H
diff --git a/src/instance/graph/Graph.cpp b/src/instance/graph/Graph.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..955625188371140260331ea61f3ce0388be85cba
--- /dev/null
+++ b/src/instance/graph/Graph.cpp
@@ -0,0 +1,285 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#ifdef DEBUG
+#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
+#else
+#define DEBUG_MSG(str) do { } while ( false )
+#endif
+
+#include <sstream>
+#include <charconv>
+#include <random>
+#include <algorithm>
+#include "Node.h"
+#include "../../services/CSV/CSVRange.h"
+#include "Graph.h"
+
+std::vector<Line> Graph::addLine(Line& line) {
+    //Add line stops to nodes
+    for(int i = 0; i < line.size(); ++i)
+    {
+        nodesVector.at(line.getNode(i)).addBusLine(line, i);
+    }
+    //Add transit line to transit lines vector
+    transitLines.push_back(line);
+    return transitLines;
+}
+
+Graph::Graph(const std::string& nodesFilePath, const std::string& edgesFilePath, const std::string& ptLinesFilePath) {
+
+    //Nodes instantiation
+    std::ifstream nodesFile(nodesFilePath);
+    std::cout << "Nodes instantiation" << std::endl;
+    for(auto& row: CSVRange(nodesFile))
+    {
+        parseNodeRow(row);
+    }
+
+    //Edges instantiation
+    std::ifstream edgesFile(edgesFilePath);
+    std::cout << "Edges instantiation" << std::endl;
+    for(auto& row: CSVRange(edgesFile))
+    {
+        this->parseEdgeRow(row);
+    }
+
+    //PT Lines instantiation
+    std::mt19937 rng;
+    rng.seed(123456789);
+    std::uniform_int_distribution<uint32_t> uint_dist10(1,10);
+    std::uniform_int_distribution<uint32_t> uint_dist60(1,60);
+    std::ifstream ptLinesFile(ptLinesFilePath);
+    std::cout << "Lines instantiation" << std::endl;
+    for(auto& row: CSVRange(ptLinesFile))
+    {
+        //If no header, do the thing
+        if(!static_cast<std::string>(row[0]).starts_with('#'))
+        {
+            parseLineRandomizedSchedule(row, rng, uint_dist10, uint_dist60);
+        }
+    }
+    std::cout << "test is done" << std::endl;
+}
+
+Graph::Graph(const std::string& datFilePath) {
+    std::ifstream infile(datFilePath);
+    DATRow currentRow = DATRow(',');
+    std::string currentLine;
+
+    //-- Read params
+    infile >> currentRow;
+    std::cout << currentRow[0] << std::endl;
+    // Seeded random number generator
+    infile >> currentRow;
+    unsigned long rngSeed;
+    std::from_chars(currentRow[1].data(), currentRow[1].data() + currentRow[1].length(), rngSeed);
+    auto rng = std::mt19937(rngSeed);
+    //-- End of params
+
+    //-- Read nodes
+    infile >> currentRow; // Read and print comment line for format
+    std::cout << currentRow.toString() << std::endl;
+    while(infile >> currentRow && !currentRow[0].starts_with('#')) {
+        this->parseNodeRow(currentRow);
+    }
+    //-- End of nodes
+
+    //-- Read Edges
+    std::cout << currentRow.toString() << std::endl;
+    while(infile >> currentRow && !currentRow[0].starts_with('#')) {
+        this->parseEdgeRow(currentRow);
+    }
+    //-- End of edges
+
+    //-- Read Public transit line
+    std::cout << currentRow.toString() << std::endl;
+    std::uniform_int_distribution<uint32_t> uint_dist10(1,10);
+    std::uniform_int_distribution<uint32_t> uint_dist60(1,60);
+    while(infile >> currentRow && !currentRow[0].starts_with('#')) {
+        this->parseLineRandomizedSchedule(currentRow, rng, uint_dist10, uint_dist60);
+    }
+}
+
+namespace fs = std::filesystem;
+void Graph::exportGraphToFiles(fs::path exportFolderPath) {
+    fs::create_directories(exportFolderPath);
+
+    //Nodes
+    std::ofstream outfileNodes(exportFolderPath.string() + "nodes.txt", std::ofstream::out | std::ofstream::trunc); //open and clear file if it already existed
+    for(auto& node : this->nodesVector)
+    {
+        outfileNodes << node.getX() << " " << node.getY() << std::endl;
+    }
+    outfileNodes.close();
+
+    //Edges
+    std::ofstream outfileEdges(exportFolderPath.string() + "edges.txt", std::ofstream::out |  std::ofstream::trunc); //open and clear file if it already existed
+    for(auto& edge : this->edgesVector)
+    {
+        outfileEdges << edge.getNodeStart() << " " << edge.getNodeEnd() << " " << edge.getLength() << std::endl;
+    }
+    outfileEdges.close();
+
+    //Transit lines
+    std::ofstream outfilePT(exportFolderPath.string() + "ptlines.txt", std::ofstream::out |  std::ofstream::trunc); //open and clear file if it already existed
+    for(auto& ptline : this->transitLines)
+    {
+        //Print nodes in order on one line
+        std::ostringstream ossNodes;
+        std::vector<int> lineNodesVector = ptline.getNodes();
+        if (!lineNodesVector.empty())
+        {
+            // Convert all but the last element to avoid a trailing ","
+            std::copy(lineNodesVector.begin(), lineNodesVector.end()-1,
+                      std::ostream_iterator<int>(ossNodes, " "));
+
+            // Now add the last element with no delimiter
+            ossNodes << lineNodesVector.back();
+        }
+        std::cout << ossNodes.view() << std::endl;
+        outfilePT << ossNodes.str() << std::endl;
+        ossNodes.clear();
+
+        //Reuse string stream to print schedules line by line
+        for(auto& schedule : ptline.getTimetables())
+        {
+            std::ostringstream ossSchedule;
+            if (!schedule.empty())
+            {
+                // Convert all but the last element to avoid a trailing ","
+                std::copy(schedule.begin(), schedule.end()-1,
+                          std::ostream_iterator<int>(ossSchedule, " "));
+
+                // Now add the last element with no delimiter
+                ossSchedule << schedule.back();
+            }
+            std::cout << ossSchedule.view() << std::endl;
+            outfilePT << ossSchedule.str() << std::endl;
+            ossSchedule.clear();
+        }
+        outfilePT << "#PT line end" <<std::endl;
+    }
+    outfilePT.close();
+    std::cout << "results of graph validations : " << this->check() << std::endl;
+}
+
+bool Graph::check() {
+    bool checkResult = true;
+    for(auto& transitLine : this->transitLines)
+    {
+        checkResult &= transitLine.check();
+    }
+    checkResult &= checkLineToNodeLinks();
+    return checkResult;
+}
+
+bool Graph::checkLineToNodeLinks() {
+    int nodeIndexFromLine;
+    Node* nodeFromGraph; //Forced to init here
+
+    bool checkResult = true;
+    for(auto& node : nodesVector)
+    {
+        for(auto& lineStop : node.getPTLinesSet())
+        {
+            nodeIndexFromLine = lineStop.getLineRef().getNode(lineStop.getStopIndex());
+            nodeFromGraph = &this->nodesVector.at(nodeIndexFromLine);
+            checkResult &= *nodeFromGraph == node;
+        }
+    }
+    return checkResult;
+}
+
+void Graph::parseNodeRow(const DATRow& row)
+{
+    //Skip line if it starts with a # (comment sign)
+    if(!static_cast<std::string>(row[0]).starts_with('#')) {
+        Status status;
+        double x, y;
+
+        status = Node::statusFromString(std::string(row[0]));
+        std::from_chars(row[1].data(), row[1].data() + row[1].length(), x);
+        std::from_chars(row[2].data(), row[2].data() + row[2].length(), y);
+
+        this->nodesVector.emplace_back(status, x, y);
+        DEBUG_MSG("Created new node " << x << " " << y << " with status = " << status);
+    }
+}
+
+void Graph::parseEdgeRow(const DATRow& row)
+{
+    int edge_start;
+    int edge_end;
+    double length;
+    //Skip line if it starts with a # (comment sign)
+    if(!static_cast<std::string>(row[0]).starts_with('#'))
+    {
+        std::from_chars(row[0].data(), row[0].data() + row[0].size(), edge_start);
+        std::from_chars(row[1].data(), row[1].data() + row[1].size(), edge_end);
+        std::from_chars(row[2].data(), row[2].data() + row[2].size(), length);
+        this->createAndAddEdge(edge_start, edge_end, length);
+        DEBUG_MSG("Created new edge between " << edge_start << " and " << edge_end << " of length " << length);
+    }
+}
+
+void Graph::parseLineRandomizedSchedule(const DATRow& row, std::mt19937 rng,
+                                        std::uniform_int_distribution<uint32_t> travelTimeDistribution,
+                                        std::uniform_int_distribution<uint32_t> startTimeDistribution)
+{
+    int startTime, endTime, frequency, currentNodeIdx;
+    //add nodes for the line
+    Line newLine = Line();
+    //Give it an ID
+    //TODO : use proper IDs in parsing for line names
+    newLine.setLineId(std::to_string(this->transitLines.size()));
+
+    //Create a base timetable. It'll be used as a basis to generate subsequent nodes' timetables
+    std::vector<int> timeTable;
+    std::from_chars(row[0].data(), row[0].data() + row[0].size(), frequency);
+    std::from_chars(row[1].data(), row[1].data() + row[1].size(), startTime);
+    std::from_chars(row[2].data(), row[2].data() + row[2].size(), endTime);
+    int currentTime = startTime + startTimeDistribution(rng); //random startTime time with 60min max offset
+    while(currentTime + frequency < endTime)
+    {
+        timeTable.push_back(currentTime + frequency);
+        currentTime += frequency;
+    }
+    newLine.addTimetable(timeTable);
+
+    for(int i = 3; i < row.size(); ++i)
+    {
+        std::from_chars(row[i].data(), row[i].data() + row[i].size(), currentNodeIdx);
+        newLine.addNode(currentNodeIdx);
+    }
+
+    //Create subsequent timetables according to preceding timetable and travel time
+    for(int i = 1; i < newLine.getNodes().size(); ++i)
+    {
+        int travelTime = travelTimeDistribution(rng); //FIXME travel time is randomized for now, we should get edge length if it exists I guess
+        std::vector<int> precedingTimeTable = newLine.getTimetable(i - 1);
+        std::vector<int> newTimetable;
+        for(auto it = precedingTimeTable.begin(); it != precedingTimeTable.end(); ++it)
+        {
+            newTimetable.emplace_back(*it.base() + travelTime);
+        }
+        newLine.addTimetable(newTimetable);
+        newTimetable.clear();
+    }
+
+    this->addLine(newLine);
+
+    DEBUG_MSG("Created new line with nodes");
+
+}
+
+void Graph::createAndAddEdge(int edgeStartNodeIndex, int edgeEndNodeIndex, double length) {
+    edgesVector.emplace_back(edgeStartNodeIndex, edgeEndNodeIndex, length);
+
+    Node entryNode = nodesVector.at(edgeStartNodeIndex);
+    entryNode.getOutgoingEdges().emplace_back(edgesVector.size() - 1);
+
+    Node exitNode = nodesVector.at(edgeEndNodeIndex);
+    exitNode.getIncomingEdges().emplace_back(edgesVector.size() - 1);
+}
diff --git a/src/instance/graph/Graph.h b/src/instance/graph/Graph.h
new file mode 100644
index 0000000000000000000000000000000000000000..9ad45fe3001ecb9709ddea97659d2438e87b10c7
--- /dev/null
+++ b/src/instance/graph/Graph.h
@@ -0,0 +1,150 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#ifndef GREEDYALGORITHM_GRAPH_H
+#define GREEDYALGORITHM_GRAPH_H
+
+
+#include <utility>
+#include <string>
+#include <random>
+#include <filesystem>
+
+#include "Node.h"
+#include "Edge.h"
+
+class DATRow;
+class Graph {
+private:
+    std::vector<Node> nodesVector; //The full list of nodes created in the graph
+    std::vector<Edge> edgesVector;
+    std::vector<Line> transitLines;
+
+    /**
+     * For every LineStop on every node of the graph, verify the node returned by looking into LineStop->Line(stopIdx)
+     * is the same as the node where the LineStop is defined
+     * @return True if @this is properly referenced in the Line object at the expected index
+     */
+    bool checkLineToNodeLinks();
+    void parseNodeRow(const DATRow& row);
+    void parseEdgeRow(const DATRow& row);
+    void parseLineRandomizedSchedule(const DATRow &row, std::mt19937 rng,
+                                     std::uniform_int_distribution<uint32_t> travelTimeDistribution,
+                                     std::uniform_int_distribution<uint32_t> startTimeDistribution);
+
+public:
+    [[nodiscard]] const std::vector<Node> &getNodesVector() const {
+        return nodesVector;
+    }
+
+    [[nodiscard]] const Node &getNode(int nodeIndex) const {
+        return nodesVector.at(nodeIndex);
+    }
+
+    [[nodiscard]] size_t getNbNodes() const {
+        return nodesVector.size();
+    }
+
+    [[nodiscard]] std::vector<LineStop> getPTLinesSet(int nodeIndex) const {
+        return nodesVector.at(nodeIndex).getPTLinesSet();
+    }
+
+    [[nodiscard]] size_t getNbPTLines(int nodeIndex) const {
+        return nodesVector.at(nodeIndex).getPTLinesSet().size();
+    }
+
+    /**
+     * @return The graph's edge vector
+     */
+    [[nodiscard]] const std::vector<Edge> &getEdgesVector() const {
+        return edgesVector;
+    }
+
+    [[nodiscard]] size_t getNbIncomingEdges(int nodeIndex) const {
+        return nodesVector.at(nodeIndex).getIncomingEdges().size();
+    }
+
+    [[nodiscard]] size_t getNbOutgoingEdges(int nodeIndex) const {
+        return nodesVector.at(nodeIndex).getOutgoingEdges().size();
+    }
+
+
+    [[nodiscard]] size_t getNbEdges(int nodeIndex) const {
+        return nodesVector.at(nodeIndex).getIncomingEdges().size() + nodesVector.at(nodeIndex).getOutgoingEdges().size();
+    }
+
+    /**
+     * @return A vector containing the list of transit lines defined in our graph (effectively G^TP)
+     */
+    [[nodiscard]] const std::vector<Line> &getPTLines() const {
+        return transitLines;
+    }
+
+    /**
+     * Pushes a reference to the given edge at the end of the graph's edge vector
+     * @param edge The edge to push in the graph's edge vector
+     * @return The edge vector after having added the edge
+     */
+    std::vector<Edge> addEdge(Edge& edge) {
+        edgesVector.push_back(edge);
+        return edgesVector;
+    }
+
+    /**
+     * Pushes a reference to the given node at the end of the graph's node vector
+     * @param node The node to push in the graph's node vector
+     * @return The node vector after having added the node
+     */
+    std::vector<Node> addNode(Node& node) {
+        nodesVector.push_back(node);
+        return nodesVector;
+    }
+
+    /**
+     * Creates a Graph and fills it with data from a single dat file with :
+     * nodes, edges and public transit data. Each separated by a #comment line.
+     * Each object is a line in the file
+     * @param datFilePath relative or absolute path to the dat file to import objects from
+     */
+    explicit Graph(const std::string& datFilePath);
+    /**
+     * Creates a Graph and fills it with data from individual files for each object type :
+     * nodes, edges and public transit data. Lines starting by # will be considered comments and skipped.
+     * Each object is a line in the file
+     * @param nodeFilePath The file containing node data (format for each line : status,x,y)
+     * @param edgeFilePath The file containing edge data (format for each line : start_node_index,end_node_index,edge_length)
+     * @param ptLineFilePath
+     */
+    Graph(const std::string& nodeFilePath, const std::string& edgeFilePath, const std::string& ptLineFilePath);
+
+    /**
+     * Adds a new Line to the graph.
+     * @param line The Line to add. It's expected to be fully filled with its nodes schedules before adding to the graph
+     * @return The updated vector containing all lines of the graph
+     */
+    std::vector<Line> addLine(Line& line);
+    /**
+     * Export graph data (nodes, edges, transit lines) to files to plot graph data with Python
+     * The function creates the folders if need be, and will overwrite existing files if data has been outputted to this folder before
+     * @param exportFolderPath A path to a folder where we can export edges.txt, nodes.txt and ptlines.txt
+     */
+    void exportGraphToFiles(std::filesystem::path exportFolderPath);
+
+    /**
+     * Executes defined check functions on every node and transit line in the graph
+     * @return True if all checks were successful, false otherwise
+     */
+    bool check();
+
+    /**
+     * Adds a new edge at the back of the edgesVector, and properly links this edge to its entry and exit nodes
+     * @param edgeStartNodeIndex Index of the node serving as a starting point for this edge
+     * @param edgeEndNodeIndex Index of the node serving as an exit point for this edge
+     * @param length The length of this edge (in minutes)
+     */
+    void createAndAddEdge(int edgeStartNodeIndex, int edgeEndNodeIndex, double length);
+};
+
+
+#endif //GREEDYALGORITHM_GRAPH_H
diff --git a/src/instance/graph/Line.cpp b/src/instance/graph/Line.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a07e3074e2ad791c34d496987c3ed0b34e49e842
--- /dev/null
+++ b/src/instance/graph/Line.cpp
@@ -0,0 +1,36 @@
+//
+// Created by rbernard on 29/01/2024.
+//
+
+#include "Line.h"
+
+bool Line::check() const{
+    return checkSchedules(); //Add any new check function here (if useful, add parameters to check() to only do certain checks
+}
+
+bool Line::checkSchedules() const{
+    bool checkResult = true;
+    checkResult &= _nodes.size() == _timetables.size(); //check we have as many schedules as nodes in our line
+
+    int precedingTimeStep = 0;
+    int expectedScheduleSize = !_timetables.empty() ? _timetables.at(0).size() : 0;
+    for(auto& schedule : _timetables)
+    {
+        precedingTimeStep = 0; //reinit first timestep to 0
+        checkResult &= schedule.size() == expectedScheduleSize; //every schedule should are the same size
+        for(auto& currentTimestep : schedule) //check timestep precedence
+        {
+            checkResult &= currentTimestep > precedingTimeStep;
+            precedingTimeStep = currentTimestep;
+        }
+    }
+    return checkResult;
+}
+
+const std::string &Line::getLineId() const {
+    return _lineID;
+}
+
+void Line::setLineId(const std::string &lineId) {
+    _lineID = lineId;
+}
diff --git a/src/instance/graph/Line.h b/src/instance/graph/Line.h
new file mode 100644
index 0000000000000000000000000000000000000000..c9bc6f73b0c39cfad62dbbcc9739019a187e14bb
--- /dev/null
+++ b/src/instance/graph/Line.h
@@ -0,0 +1,68 @@
+//
+// Created by rbernard on 29/01/2024.
+//
+
+#ifndef GREEDYALGORITHM_LINE_H
+#define GREEDYALGORITHM_LINE_H
+
+
+#include <utility>
+#include <vector>
+#include <string>
+#include "../../utils/SearchAlgorithms.h"
+
+class Line {
+private:
+    std::string _lineID;
+    std::vector<int> _nodes; //index according to Graph::_nodes, sorted according to line order (start -> terminus)
+    std::vector<std::vector<int>> _timetables; //list of list of timestamps for each node start order (size of _timetables must remain constant throughout the whole vector
+public:
+    Line() = default;
+
+    [[nodiscard]] const std::string &getLineId() const;
+    void setLineId(const std::string &lineId);
+    [[nodiscard]] std::vector<int> getNodes() const { return _nodes;};
+    [[nodiscard]] int getNode(int index) const { return _nodes.at(index);};
+    void addNode(const int node){this->_nodes.emplace_back(node);};
+    [[nodiscard]] bool isEmpty() const{return this->_nodes.empty() || _timetables.empty();}
+
+    [[nodiscard]] std::vector<int> getTimetable(int pos) const{ return _timetables.at(pos);};
+    [[nodiscard]] std::vector<std::vector<int>> getTimetables() const{ return _timetables;};
+    void addTimetable(const std::vector<int>& timetable) { _timetables.push_back(timetable);};
+    void setTimetable(int pos, const std::vector<int>& timetable) { _timetables.insert(_timetables.begin() + pos, timetable);};
+
+    /**
+     * Searches for the next scheduled passage at a given station after a given instant O(log n)
+     * @param stationIdx The station number for which we want to search the schedule
+     * @param instant The instant of arrival at the station, hence we look for the value itself or the first greater instant
+     * @return a vector index corresponding to the next valid passage at the given station and after or at the given instant
+     */
+    [[nodiscard]] size_t findNextScheduledPassage(int stationIdx, int instant) const {
+        return SearchAlgorithms<int>::findNextSortedValue(_timetables.at(stationIdx), instant);
+    }
+    /**
+     * Returns the instant for the given station at a given schedule position, O(1)
+     * @param stationIdx The station number for which we want to search the schedule
+     * @param scheduleIdx The expected index of our passage
+     * @return The instant associated with the given station at the given schedule position
+     */
+    [[nodiscard]] int getInstant(int stationIdx, int scheduleIdx) const { return _timetables.at(stationIdx).at(scheduleIdx); }
+
+    [[nodiscard]] size_t size() const {return _nodes.size();}
+    [[nodiscard]] size_t scheduleSize() const {return _timetables.empty() ? 0 : _timetables.at(0).size();}
+
+    [[nodiscard]] bool check() const;
+    [[nodiscard]] bool checkSchedules() const;
+
+    bool operator==(const Line &rhs) const {
+        return _lineID == rhs.getLineId();
+    }
+
+    bool operator!=(const Line &rhs) const {
+        return _lineID != rhs.getLineId();
+    }
+};
+
+#include "Node.h"
+
+#endif //GREEDYALGORITHM_LINE_H
diff --git a/src/instance/graph/LineStop.cpp b/src/instance/graph/LineStop.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1c8a789076ba36f24bcca26096804711c4e2fe45
--- /dev/null
+++ b/src/instance/graph/LineStop.cpp
@@ -0,0 +1,5 @@
+//
+// Created by rbernard on 30/01/24.
+//
+
+#include "LineStop.h"
diff --git a/src/instance/graph/LineStop.h b/src/instance/graph/LineStop.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d15019cab6313ceab02cec3756cb18d001c4202
--- /dev/null
+++ b/src/instance/graph/LineStop.h
@@ -0,0 +1,72 @@
+//
+// Created by rbernard on 30/01/24.
+//
+
+#ifndef GREEDYALGORITHM_LINESTOP_H
+#define GREEDYALGORITHM_LINESTOP_H
+
+
+#include <utility>
+
+#include "Line.h"
+
+class LineStop {
+private:
+    Line _lineRef; //reference to the line
+    int _stopIndex{}; //index for the stop relative to this node in the Line object
+
+public:
+    LineStop(const Line& lineRef, int stopIndex) : _lineRef(lineRef), _stopIndex(stopIndex) {}
+
+    [[nodiscard]] Line getLineRef() const {
+        return _lineRef;
+    }
+
+    [[nodiscard]] int getInstant(int stationIdx, int scheduleIdx) const { return _lineRef.getInstant(stationIdx, scheduleIdx); }
+    [[nodiscard]] int findNextScheduledPassage(int stationIdx, int instant) const { return _lineRef.findNextScheduledPassage(stationIdx, instant); }
+
+    /**
+     * @return -1 if there are no valid successors to this LineStop's node. Returns the node index in the graph if there is a valid successor
+     */
+    [[nodiscard]] int getNextNodeIndex() const
+    {
+        if(_stopIndex + 1 < _lineRef.getNodes().size())
+            return _lineRef.getNode(_stopIndex + 1);
+        else
+            return -1;
+    }
+
+    /**
+     * @return -1 if there are no valid successors to this LineStop's node. Returns the node index in the graph if there is a valid successor
+     */
+    [[nodiscard]] int getPrecedingNodeIndex() const
+    {
+        if(_stopIndex - 1 >= 0)
+            return _lineRef.getNode(_stopIndex - 1);
+        else
+            return -1;
+    }
+
+    [[nodiscard]] int getStopIndex() const {
+        return _stopIndex;
+    }
+
+    void setStopIndex(int stopIndex) {
+        _stopIndex = stopIndex;
+    }
+
+    [[nodiscard]] bool isEmpty() const{
+        return _lineRef.isEmpty();
+    }
+
+    bool operator<(LineStop const& rhs) const {return &this->_lineRef < &rhs._lineRef;} //just check line addresses. Basically we just don't want the exact same line twice
+
+    LineStop& operator=(LineStop const& rhs) {
+        _lineRef = rhs.getLineRef();
+        _stopIndex = rhs.getStopIndex();
+        return *this;
+    }
+};
+
+
+#endif //GREEDYALGORITHM_LINESTOP_H
diff --git a/src/instance/graph/Node.cpp b/src/instance/graph/Node.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6b6f7d99ae1a66a07847edca73f086d258a4b29d
--- /dev/null
+++ b/src/instance/graph/Node.cpp
@@ -0,0 +1,49 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#include "Node.h"
+
+/**
+ * Default constructor, coordinates default to negatives and _status to work
+ */
+Node::Node() {
+    _status = Status::work;
+    _x = -1;
+    _y = -1;
+}
+
+bool Node::isPTNode() {
+    return &_ptLines == nullptr || _ptLines.empty();
+}
+
+Node::Node(Status status, double x, double y) : _status(status), _x(x), _y(y), _ptLines(std::vector<LineStop>()) {
+    this->_status = status;
+    this->_x = x;
+    this->_y = y;
+}
+
+void Node::addBusLine(const Line& line, int indexInLine) {
+    this->_ptLines.emplace_back(line, indexInLine);
+}
+
+bool Node::operator==(const Node &rhs) const {
+    return _x == rhs.getX()
+        && _y == rhs.getY()
+        && _status == rhs.getStatus();
+}
+
+bool Node::operator!=(const Node &rhs) const {
+    return _x != rhs.getX()
+           || _y != rhs.getY()
+           || _status != rhs.getStatus();
+}
+
+Status  Node::statusFromString(std::string from) {
+    auto pair = stringToStatusMap.find(from);
+    if (pair != stringToStatusMap.end()) {
+        return pair->second;
+    } else {
+        return Status::work;
+    }
+}
diff --git a/src/instance/graph/Node.h b/src/instance/graph/Node.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e6ee2c393cf1b30672c58d69fb35b9417f39512
--- /dev/null
+++ b/src/instance/graph/Node.h
@@ -0,0 +1,96 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#ifndef GREEDYALGORITHM_NODE_H
+#define GREEDYALGORITHM_NODE_H
+
+#include <string>
+#include <unordered_set>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+/**
+ * Status used to qualify nodes as places with different goals (work, leisure, residential)
+ * Useful for a semi-random requests generation
+ */
+enum Status {
+    work,
+    leisure,
+    residential
+};
+
+static std::unordered_map<std::string,Status> const stringToStatusMap = {{"work",        Status::work},
+                                                                         {"leisure",     Status::leisure},
+                                                                         {"residential", Status::residential} };
+
+class Line;
+class LineStop;
+class Node {
+private:
+    Status _status;
+    double _x;
+    double _y;
+    std::vector<LineStop> _ptLines;
+    std::vector<int> _incomingEdgesIndex; //List of edge index in the graph structure for all edges leading to this node
+    std::vector<int> _outgoingEdgesIndex; //List of edge index in the graph structure for all edges leading to this node
+    //TODO : Should these vectors be considered complete over the whole set of nodes ? Probably ? Considering we will probably pre-process shortest paths between all SAEV stations
+
+public:
+    Node();
+    Node(Status status, double x, double y);
+
+    /**
+     * Simple struct to return full coordinates data with an x and y pair
+     */
+    struct Coordinate {
+        double x,y;
+    };
+
+    /**
+     * @return True if this node contains one or more LineStop
+     */
+    bool isPTNode();
+    /**
+     * Adds a new LineStop to the LineStop set
+     * @param line the line referenced by the LineStop
+     * @param indexInLine The station index in the Line for the current node
+     */
+    void addBusLine(const Line& line, int indexInLine);
+    /**
+     * Parses the string parameter and converts it to an appropriate Status value
+     * @param from String serving as basis for conversion (trailing spaces and capitalization don't matter)
+     * @return A status depending on the given string. If no status corresponds to the given String, will default to Work
+     */
+    static Status statusFromString(std::string from) ;
+    /**
+     * Formats x and y data in a Coordinate object and returns it
+     * @return A new Coordinate object with x and y data
+     */
+    [[nodiscard]] Coordinate getCoordinates() const {return Coordinate(_x,_y);}
+    [[nodiscard]] double getX() const {return _x;}
+    [[nodiscard]] double getY() const {return _y;}
+    [[nodiscard]] Status getStatus() const {return _status;}
+    [[nodiscard]] std::vector<int> getIncomingEdges() const {return _incomingEdgesIndex;}
+    [[nodiscard]] std::vector<int> getOutgoingEdges() const {return _outgoingEdgesIndex;}
+    [[nodiscard]] std::vector<LineStop> getPTLinesSet() const {return _ptLines;}
+
+    /**
+     * Verify if _x, _y and _status are equal to check for node equality
+     * @param rhs right hand side of the comparison operator
+     * @return True iff _x, _y and _status are equal between both sides of the operator
+     */
+    bool operator==(const Node& rhs) const;
+    /**
+     * Verify if _x, _y or _status are different to check for node difference
+     * @param rhs right hand side of the comparison operator
+     * @return True if any of _x, _y and _status is not equal between both sides of the operator, false otherwise
+     */
+    bool operator!=(const Node& rhs) const;
+};
+#include "Line.h"
+#include "LineStop.h"
+
+
+#endif //GREEDYALGORITHM_NODE_H
diff --git a/src/instance/requests/Request.cpp b/src/instance/requests/Request.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a3625a3bc5818728bf48d8c5722f3874668be457
--- /dev/null
+++ b/src/instance/requests/Request.cpp
@@ -0,0 +1,5 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#include "Request.h"
diff --git a/src/instance/requests/Request.h b/src/instance/requests/Request.h
new file mode 100644
index 0000000000000000000000000000000000000000..775006c19e7a00b287e528f70936399c182715ba
--- /dev/null
+++ b/src/instance/requests/Request.h
@@ -0,0 +1,14 @@
+//
+// Created by rbernard on 22/01/24.
+//
+
+#ifndef GREEDYALGORITHM_REQUEST_H
+#define GREEDYALGORITHM_REQUEST_H
+
+
+class Request {
+
+};
+
+
+#endif //GREEDYALGORITHM_REQUEST_H
diff --git a/src/main.cpp b/src/main.cpp
deleted file mode 100644
index bc8f460d02aeb0695e7840121bc4caefe788a878..0000000000000000000000000000000000000000
--- a/src/main.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-#include <iostream>
-
-int main() {
-    std::cout << "Hello, World!" << std::endl;
-    return 0;
-}
diff --git a/src/services/CSV/CSVIterator.h b/src/services/CSV/CSVIterator.h
new file mode 100644
index 0000000000000000000000000000000000000000..4bfa30e998566c73b07015787f3b65a1c1b67076
--- /dev/null
+++ b/src/services/CSV/CSVIterator.h
@@ -0,0 +1,37 @@
+//
+// Created by rbernard on 29/01/2024.
+// Source : https://stackoverflow.com/a/1120224
+
+#ifndef GREEDYALGORITHM_CSVITERATOR_H
+#define GREEDYALGORITHM_CSVITERATOR_H
+
+
+#include "CSVRow.h"
+
+class CSVIterator {
+    typedef std::input_iterator_tag     iterator_category;
+    typedef CSVRow                      value_type;
+    typedef std::size_t                 difference_type;
+    typedef CSVRow*                     pointer;
+    typedef CSVRow&                     reference;
+
+public:
+    CSVIterator(std::istream& str)  :m_str(str.good()?&str:nullptr) { ++(*this); }
+    CSVIterator()                   :m_str(nullptr) {}
+
+    // Pre Increment
+    CSVIterator& operator++()               {if (m_str) { if (!((*m_str) >> m_row)){m_str = nullptr;}}return *this;}
+    // Post increment
+    CSVIterator operator++(int)             {CSVIterator    tmp(*this);++(*this);return tmp;}
+    CSVRow const& operator*()   const       {return m_row;}
+    CSVRow const* operator->()  const       {return &m_row;}
+
+    bool operator==(CSVIterator const& rhs) {return ((this == &rhs) || ((this->m_str == nullptr) && (rhs.m_str == nullptr)));}
+    bool operator!=(CSVIterator const& rhs) {return !((*this) == rhs);}
+private:
+    std::istream*       m_str;
+    CSVRow              m_row;
+};
+
+
+#endif //GREEDYALGORITHM_CSVITERATOR_H
diff --git a/src/services/CSV/CSVRange.h b/src/services/CSV/CSVRange.h
new file mode 100644
index 0000000000000000000000000000000000000000..d2ab2bd46c0dcee4bffb7498f3da2e591c9b323c
--- /dev/null
+++ b/src/services/CSV/CSVRange.h
@@ -0,0 +1,23 @@
+//
+// Created by rbernard on 29/01/2024.
+// Source : https://stackoverflow.com/a/1120224
+
+#ifndef GREEDYALGORITHM_CSVRANGE_H
+#define GREEDYALGORITHM_CSVRANGE_H
+
+
+#include <istream>
+#include "CSVIterator.h"
+
+class CSVRange {
+    std::istream&   stream;
+public:
+    CSVRange(std::istream& str)
+            : stream(str)
+    {}
+    CSVIterator begin() const {return CSVIterator{stream};}
+    CSVIterator end()   const {return CSVIterator{};}
+};
+
+
+#endif //GREEDYALGORITHM_CSVRANGE_H
diff --git a/src/services/CSV/CSVRow.h b/src/services/CSV/CSVRow.h
new file mode 100644
index 0000000000000000000000000000000000000000..c12f219c0af369a5737dc74265b2a35807eda9de
--- /dev/null
+++ b/src/services/CSV/CSVRow.h
@@ -0,0 +1,11 @@
+//
+// Created by rbernard on 29/01/2024.
+// Source : https://stackoverflow.com/a/1120224
+
+#include "../DatFile/DATRow.h"
+
+class CSVRow : public DATRow
+{
+public:
+    CSVRow() : DATRow(','){}
+};
diff --git a/src/services/DatFile/DATRow.h b/src/services/DatFile/DATRow.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f217d9fdeb893c125865f796f826d9ea995d136
--- /dev/null
+++ b/src/services/DatFile/DATRow.h
@@ -0,0 +1,56 @@
+//
+// Created by rbernard on 29/01/2024.
+// Adapted from : https://stackoverflow.com/a/1120224
+
+#include <iterator>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <string>
+
+class DATRow
+{
+public:
+    std::string_view operator[](std::size_t index) const
+    {
+        return std::string_view(&m_line[m_data[index] + 1], m_data[index + 1] -  (m_data[index] + 1));
+    }
+
+    [[nodiscard]] std::size_t size() const
+    {
+        return m_data.size() - 1;
+    }
+
+    void readNextRow(std::istream& str)
+    {
+        std::getline(str, m_line);
+
+        m_data.clear();
+        m_data.emplace_back(-1);
+        std::string::size_type pos = 0;
+        while((pos = m_line.find(_separator, pos)) != std::string::npos)
+        {
+            m_data.emplace_back(pos);
+            ++pos;
+        }
+        // This checks for a trailing comma with no data after it.
+        pos   = m_line.size();
+        m_data.emplace_back(pos);
+    }
+
+    std::string toString() {return m_line;}
+
+    DATRow() : _separator(' ') {}
+    explicit DATRow(const char separator) : _separator(separator) {}
+protected:
+    const char          _separator;
+    std::string         m_line;
+    std::vector<int>    m_data;
+};
+
+inline std::istream& operator>>(std::istream& str, DATRow& data)
+{
+    data.readNextRow(str);
+    return str;
+}
\ No newline at end of file
diff --git a/src/services/visualisation/graph_vis.py b/src/services/visualisation/graph_vis.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c0ca483c0a9afb068db30a8c88f460eea5aa436
--- /dev/null
+++ b/src/services/visualisation/graph_vis.py
@@ -0,0 +1,78 @@
+import matplotlib.pyplot as plt
+
+#visualisation params
+eps = 0.2 #decalage du label par rapport au point
+
+
+class Edge:
+    def __init__(self):
+        self.start = None
+        self.end = None
+        self.length = None
+
+    def __init__(self, start: int, end: int, length: float):
+        self.start = start
+        self.end = end
+        self.length = length
+
+
+class Node:
+    def __init__(self):
+        self.status = None
+        self.x = None
+        self.y = None
+
+    def __init__(self, x: float, y: float, status: str = "work"):
+        self.status = status
+        self.x = x
+        self.y = y
+
+
+#TODO : get those from cpp
+nodes = []
+edges = []
+i = 0
+N = len(nodes)
+instanceFolderPath = "resources/test/outputs/basic_debug_instance/"
+
+#Read nodes
+fp = open(instanceFolderPath + 'nodes.txt')
+lines = fp.read().split("\n")
+fp.close()
+for nodeLine in lines:
+    splitLine = nodeLine.split(" ")
+    if len(splitLine) == 2:
+        nodes.append(Node(float(splitLine[0]), float(splitLine[1])))
+
+#Read edges
+fp = open(instanceFolderPath + 'edges.txt')
+lines = fp.read().split("\n")
+fp.close()
+for edgeLine in lines:
+    splitLine = edgeLine.split(" ")
+    if len(splitLine) == 3:
+        edges.append(Edge(int(splitLine[0]), int(splitLine[1]), float(splitLine[2])))
+
+
+#print points from nodes vectors
+plt.scatter([node.x for node in nodes], [node.y for node in nodes], color="red", s=50)
+
+#print labels
+while i < N:
+    suiv = (i+1)%N
+    for i in range(len(nodes)):
+        plt.text(nodes[i].x+eps, nodes[i].y-eps, i)
+    i=i+1
+
+
+#Print edges
+for edge in edges:
+    plt.annotate(text='', xy=(nodes[edge.start].x, nodes[edge.start].y),
+                 xytext=(nodes[edge.end].x, nodes[edge.end].y),
+                 arrowprops=dict(arrowstyle='<-'))
+
+
+#on agrandit un peu les limites sur X pour que les
+# labels ne dépassent pas le cadre
+plt.xlim(min([node.x for node in nodes])-eps, max([node.x for node in nodes])+2*eps)
+plt.show()
\ No newline at end of file
diff --git a/src/utils/SearchAlgorithms.h b/src/utils/SearchAlgorithms.h
new file mode 100644
index 0000000000000000000000000000000000000000..90214d26e45af0a0fff8f348da9cf16e3c5dbffb
--- /dev/null
+++ b/src/utils/SearchAlgorithms.h
@@ -0,0 +1,43 @@
+//
+// Created by romain on 20/02/24.
+//
+
+#ifndef GREEDYALGORITHM_SEARCHALGORITHMS_H
+#define GREEDYALGORITHM_SEARCHALGORITHMS_H
+
+
+#include <vector>
+
+template <typename Comparable>
+class SearchAlgorithms {
+public:
+    /**
+     * Returns the first index corresponding exactly to the given value in the
+     * @param sortedVector a sorted vector in which to search for a value. If the vector isn't sorted, there is no guarantee that we return the appropriate index or that performance is O(log n)
+     * @param value the comparable value we are looking for in the vector
+     * @return the size of the vector if all vector values are lower than our search, the index of the first larger or equal value otherwise
+     */
+    static size_t findNextSortedValue(std::vector<Comparable> sortedVector, Comparable value) {
+        auto iterator = std::lower_bound(sortedVector.begin(), sortedVector.end(), value);
+        return std::distance(sortedVector.begin(), iterator);
+    }
+
+    /**
+     * Returns the first index corresponding exactly to the given value in the vector
+     * @param sortedVector a sorted vector in which to search for a value. If the vector isn't sorted, there is no guarantee that we return the first index or that performance is O(log n)
+     * @param value the comparable value we are looking for in the vector
+     * @return -1 if the exact value hasn't been found or the index of the value if it's been found
+     */
+    static size_t vectorBinarySearch(std::vector<Comparable> sortedVector, Comparable value) {
+        auto iterator = std::lower_bound(sortedVector.begin(), sortedVector.end(), value);
+        if (iterator == sortedVector.end() || *iterator != value) {
+            return -1;
+        } else {
+            std::size_t index = std::distance(sortedVector.begin(), iterator);
+            return index;
+        }
+    }
+};
+
+
+#endif //GREEDYALGORITHM_SEARCHALGORITHMS_H
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..451d74dfa26120d1acbaca161d1fe7f2217a1644
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,23 @@
+# 'Google_test' is the subproject name
+project(Google_tests)
+
+# 'lib' is the folder with Google Test sources
+add_subdirectory(lib/googletest)
+include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
+
+# 'Google_Tests_run' is the target name
+# 'test1.cpp test2.cpp' are source files with tests
+add_executable(Line_UT
+        src/LineUnitTests.cpp
+        ../src/instance/graph/Node.cpp
+        ../src/instance/graph/Node.h
+        ../src/instance/graph/Edge.cpp
+        ../src/instance/graph/Edge.h
+        ../src/instance/graph/Graph.cpp
+        ../src/instance/graph/Graph.h
+        ../src/instance/graph/Line.cpp
+        ../src/instance/graph/Line.h
+)
+add_executable(Transit_preprocess_UT src/TransitPreprocessUnitTest.cpp)
+target_link_libraries(Line_UT gtest gtest_main)
+target_link_libraries(Transit_preprocess_UT gtest gtest_main)
\ No newline at end of file
diff --git a/test/debug.cpp b/test/debug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2b7f45f471c95fa0c78367d581bb9dd7f7dc9558
--- /dev/null
+++ b/test/debug.cpp
@@ -0,0 +1,70 @@
+//
+// Created by rbernard on 30/01/2024.
+//
+#include <iostream>
+#include "../src/instance/graph/Graph.h"
+#include "../src/ShortestPath/Transit/TransitStateContainer.h"
+#include "../src/ShortestPath/Transit/TransitShortestPathPrecompute.h"
+#include "../src/ShortestPath/Transit/TransitShortestPathContainer.h"
+
+int main() {
+//    Graph give_me_a_name("../resources/test/instances/basic_debug_instance/nodes.csv",
+//          "../resources/test/instances/basic_debug_instance/edges.csv",
+//          "../resources/test/instances/basic_debug_instance/PT_lines.csv");
+
+//    std::string instanceFolder = "basic_debug_instance/";
+    std::string instanceFolder = "contiguous_lines_debug_instance/";
+    std::string datFile = "graph.dat";
+
+    Graph graphFromSingleFile("../resources/test/instances/" + instanceFolder + datFile);
+    graphFromSingleFile.exportGraphToFiles("../resources/test/outputs/" + instanceFolder);
+    TransitShortestPathContainer contiguousContainer(graphFromSingleFile.getNbNodes());
+    for(auto& ptLine : graphFromSingleFile.getPTLines()) {
+        for(int i = 0; i < ptLine.size(); ++i) {
+            for (auto& startingTime: ptLine.getTimetable(i)) {
+                contiguousContainer.addShortestPathCollection(i, startingTime, graphFromSingleFile.getNbNodes(),
+                                                              TransitShortestPathPrecompute::executeAlgorithm(graphFromSingleFile, ptLine.getNode(i),startingTime));
+                contiguousContainer.getShortestPathsFromTime(i, startingTime - 1);
+            }
+        }
+    }
+
+    TransitShortestPathContainer crossingContainer(graphFromSingleFile.getNbNodes());
+    Graph crossingLinesGraph("../resources/test/instances/multiple_crossing_lines_debug_instance/" + datFile);
+    for(auto& ptLine : crossingLinesGraph.getPTLines()) {
+        for(int i = 0; i < ptLine.size(); ++i) {
+            for (auto& startingTime: ptLine.getTimetable(i)) {
+                crossingContainer.addShortestPathCollection(i, startingTime, crossingLinesGraph.getNbNodes(),
+                                                              TransitShortestPathPrecompute::executeAlgorithm(crossingLinesGraph, ptLine.getNode(i),startingTime));
+                crossingContainer.getShortestPathsFromTime(i, startingTime - 1);
+            }
+        }
+    }
+
+    TransitShortestPathContainer cycleContainer(graphFromSingleFile.getNbNodes());
+    Graph cyclingLineGraph("../resources/test/instances/cycling_line_debug_instance/" + datFile);
+    for(auto& ptLine : cyclingLineGraph.getPTLines()) {
+        for(int i = 0; i < ptLine.size(); ++i) {
+            for (auto& startingTime: ptLine.getTimetable(i)) {
+                cycleContainer.addShortestPathCollection(i, startingTime, cyclingLineGraph.getNbNodes(),
+                                                              TransitShortestPathPrecompute::executeAlgorithm(cyclingLineGraph, ptLine.getNode(i),startingTime));
+                cycleContainer.getShortestPathsFromTime(i, startingTime - 1);
+            }
+        }
+    }
+
+
+    TransitShortestPathContainer multiCycleContainer(graphFromSingleFile.getNbNodes());
+    Graph multipleCyclingLinesGraph("../resources/test/instances/multiple_cycling_lines_debug_instance/" + datFile);
+    for(auto& ptLine : multipleCyclingLinesGraph.getPTLines()) {
+        for(int i = 0; i < ptLine.size(); ++i) {
+            for (auto& startingTime: ptLine.getTimetable(i)) {
+                multiCycleContainer.addShortestPathCollection(i, startingTime, multipleCyclingLinesGraph.getNbNodes(),
+                                                               TransitShortestPathPrecompute::executeAlgorithm(multipleCyclingLinesGraph, ptLine.getNode(i),startingTime));
+                multiCycleContainer.getShortestPathsFromTime(i, startingTime - 1);
+            }
+        }
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/test/lib/googletest b/test/lib/googletest
new file mode 160000
index 0000000000000000000000000000000000000000..eff443c6ef5eb6ab598bfaae27f9427fdb4f6af7
--- /dev/null
+++ b/test/lib/googletest
@@ -0,0 +1 @@
+Subproject commit eff443c6ef5eb6ab598bfaae27f9427fdb4f6af7
diff --git a/test/src/LineUnitTests.cpp b/test/src/LineUnitTests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc8b3c43d45e7b6110af5f370d319f286779d995
--- /dev/null
+++ b/test/src/LineUnitTests.cpp
@@ -0,0 +1,31 @@
+//
+// Created by romain on 15/02/24.
+//
+
+//TODO : Implement following tests
+//  - verify schedule continuity (forall schedule, schedule[0] < schedule[1]
+//  -
+
+#include "../lib/googletest/googletest/include/gtest/gtest.h"
+#include "../../src/instance/graph/Graph.h"
+
+
+TEST(LineTests, LineGenerationScheduleOrder) {
+    std::string instanceFolder = "contiguous_lines_debug_instance/";
+    std::string datFile = "graph.dat";
+    Graph graphFromSingleFile("../resources/test/instances/" + instanceFolder + datFile);
+
+    for(const auto& line : graphFromSingleFile.getPTLines()) {
+        for(const auto& schedule : line.getTimetables()) {
+            for(size_t i = 0; i < line.scheduleSize(); ++i) {
+                ASSERT_GT(schedule.at(i), schedule.at(i + 1)); //assert schedule value order
+            }
+        }
+        ASSERT_TRUE(line.checkSchedules()); //assert line schedule check function is coherent with our preceding assertion
+    }
+}
+
+int main(int argc, char* argv[]) {
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/test/src/TransitPreprocessUnitTest.cpp b/test/src/TransitPreprocessUnitTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e2111003004bf6a78a09e73b1cd2495119610922
--- /dev/null
+++ b/test/src/TransitPreprocessUnitTest.cpp
@@ -0,0 +1,3 @@
+//
+// Created by romain on 20/03/24.
+//