diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4bea03d2a6eb93524b341c658c3c4fdd6f9df24b..030fcb284d7de5ab2d807fcbdf8ba9fe255a6c2f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.27)
+cmake_minimum_required(VERSION 3.28.1)
 project(GreedyAlgorithm)
 
 set(CMAKE_CXX_STANDARD 23)
@@ -7,6 +7,9 @@ add_definitions(-DDEBUG_TRANSIT_PRECOMPUTE)
 
 add_subdirectory(test)
 
+#set(PYTHON_INCLUDE_DIRS "/usr/include/python3.10/")
+find_package (Python COMPONENTS Interpreter Development)
+
 add_executable(GreedyAlgorithm
         src/instance/graph/Node.cpp
         src/instance/graph/Node.h
@@ -22,20 +25,121 @@ add_executable(GreedyAlgorithm
         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
+        src/algorithm/ShortestPath/ShortestPath.h
+        src/algorithm/ShortestPath/Transit/TransitShortestPath.h
+        src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
+        src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h
+        src/algorithm/ShortestPath/Transit/TransitAlgorithmState.cpp
+        src/algorithm/ShortestPath/Transit/TransitAlgorithmState.h
+        src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.cpp
+        src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h
+        src/routes/requests/RequestRoute.cpp
+        src/routes/requests/RequestRoute.h
+        src/routes/vehicle/SAEVRoute.cpp
+        src/routes/vehicle/SAEVRoute.h
+        src/TimeWindow.cpp
+        src/TimeWindow.h
+        src/routes/KeyPoint.cpp
+        src/routes/KeyPoint.h
+        src/routes/requests/RequestKeyPoint.cpp
+        src/routes/requests/RequestKeyPoint.h
+        src/routes/vehicle/SAEVKeyPoint.cpp
+        src/routes/vehicle/SAEVKeyPoint.h
+        src/instance/SAEVehicle.cpp
+        src/instance/SAEVehicle.h
+        src/routes/vehicle/propagation/SAEVRouteChangelist.cpp
+        src/routes/vehicle/propagation/SAEVRouteChangelist.h
+        src/routes/vehicle/propagation/SAEVRouteChange.cpp
+        src/routes/vehicle/propagation/SAEVRouteChange.h
+        "src/utils/Instance Generation/Requests/RequestsGenerator.cpp"
+        "src/utils/Instance Generation/Requests/RequestsGenerator.h"
+        "src/utils/Instance Generation/Requests/RequestGenerationParameters.h"
+        "src/utils/Instance Generation/Graph/OSRMGraphGenerator.h"
+        src/routes/vehicle/BestRequestInsertion.h
+        src/routes/vehicle/BestInsertionQueue.h
+        src/utils/Constants.h
+        src/utils/Globals.h
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp
+        src/algorithm/Multimodal/Heuristics/TransitAccess.h
+)
+
+add_executable(RequestGenerator
+        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
+        src/instance/graph/LineStop.cpp
+        src/instance/graph/LineStop.h
+        src/services/DatFile/DATRow.h
+        src/routes/vehicle/SAEVRoute.cpp
+        src/routes/vehicle/SAEVRoute.h
+        src/TimeWindow.cpp
+        src/TimeWindow.h
+        "src/utils/Instance Generation/Requests/RequestsGenerator.cpp"
+        "src/utils/Instance Generation/Requests/RequestsGenerator.h"
+        "src/utils/Instance Generation/Requests/RequestGenerationParameters.h"
+        "src/utils/Instance Generation/Graph/OSRMGraphGenerator.h"
+        src/utils/Globals.h
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h
+        src/algorithm/ShortestPath/Vehicle/VehicleShortestPath.h
+        src/algorithm/ShortestPath/Vehicle/VehiclePathState.h
+        src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
+        src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
+        src/algorithm/ShortestPath/Vehicle/MatrixShortestPathContainer.h
+        src/algorithm/ShortestPath/Vehicle/ClosestDestinationsContainer.h
+        src/algorithm/ShortestPath/Vehicle/VehicleDestination.h
 )
+
+add_executable(GraphGeneration
+        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/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
+        src/instance/graph/LineStop.cpp
+        src/instance/graph/LineStop.h
+        src/services/DatFile/DATRow.h
+        "src/utils/Instance Generation/Graph/OSRMGraphGenerator.h"
+        "src/utils/Instance Generation/Graph/GraphGenerator.h"
+        "src/utils/Instance Generation/Graph/PTLineGenerationParameters.h"
+        src/algorithm/DARP/Heuristics/BestInsertionHeuristic.cpp
+        src/algorithm/DARP/Heuristics/BestInsertionHeuristic.h
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h
+        src/algorithm/ShortestPath/Vehicle/VehicleShortestPath.h
+        src/algorithm/ShortestPath/Vehicle/VehiclePathState.h
+        src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
+        src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
+        src/algorithm/ShortestPath/Vehicle/MatrixShortestPathContainer.h
+        src/algorithm/Multimodal/Heuristics/TransitAccess.h
+)
+
+target_include_directories(GreedyAlgorithm PRIVATE ${PYTHON_INCLUDE_DIRS})
+target_link_libraries(GreedyAlgorithm ${PYTHON_LIBRARIES})
+
+add_executable(ModularHeuristic_DEB
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h
+        src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp)
diff --git a/resources/test/instances/BestInsertionHeuristic/DebugWeight/graph.dat b/resources/test/instances/BestInsertionHeuristic/DebugWeight/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..9f90059f2998d214cdfd2c9bf484850976d201a0
--- /dev/null
+++ b/resources/test/instances/BestInsertionHeuristic/DebugWeight/graph.dat
@@ -0,0 +1,30 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+,0,0
+,1,1
+,1,2
+,2,2
+,2,3
+,3,3
+,3,4
+,4,4
+,4,5
+,5,5
+,5,6
+,6,6
+,6,7
+#Matrix
+0,4,60,60,60,60,60,1,60,5,60,10,60
+60,0,15,3,60,60,60,60,60,60,60,60,60
+60,60,0,60,60,60,7,60,60,60,60,60,60
+60,60,60,0,5,5,60,60,60,60,60,60,60
+60,60,6,60,0,5,60,60,60,60,60,60,60
+60,60,60,60,2,0,16,60,60,60,60,60,60
+2,60,60,60,60,60,0,16,60,60,60,60,60
+60,60,60,60,60,60,60,0,10,3,60,60,60
+60,60,60,60,60,60,60,60,0,60,60,5,60
+60,60,60,60,60,60,60,60,6,0,18,10,60
+3,60,60,60,60,60,60,60,60,60,0,60,60
+60,60,60,60,60,60,60,60,60,60,60,0,4
+60,60,60,60,60,60,60,60,60,60,2,60,0
diff --git a/resources/test/instances/BestInsertionHeuristic/DebugWeight/requests.dat b/resources/test/instances/BestInsertionHeuristic/DebugWeight/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..43762eb6dfb6c57bd300f3052dec5c01578f10fb
--- /dev/null
+++ b/resources/test/instances/BestInsertionHeuristic/DebugWeight/requests.dat
@@ -0,0 +1,9 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+1,2,1,5,,,30,4
+1,2,1,5,,,30,3
+1,2,1,5,,,30,1
+1,2,1,5,,,30,2
+1,2,1,5,,,30,3
+1,2,1,5,,,30,2
diff --git a/resources/test/instances/Constraint Propagation/Instance_Alain_140624/graph.dat b/resources/test/instances/Constraint Propagation/Instance_Alain_140624/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..9f90059f2998d214cdfd2c9bf484850976d201a0
--- /dev/null
+++ b/resources/test/instances/Constraint Propagation/Instance_Alain_140624/graph.dat	
@@ -0,0 +1,30 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+,0,0
+,1,1
+,1,2
+,2,2
+,2,3
+,3,3
+,3,4
+,4,4
+,4,5
+,5,5
+,5,6
+,6,6
+,6,7
+#Matrix
+0,4,60,60,60,60,60,1,60,5,60,10,60
+60,0,15,3,60,60,60,60,60,60,60,60,60
+60,60,0,60,60,60,7,60,60,60,60,60,60
+60,60,60,0,5,5,60,60,60,60,60,60,60
+60,60,6,60,0,5,60,60,60,60,60,60,60
+60,60,60,60,2,0,16,60,60,60,60,60,60
+2,60,60,60,60,60,0,16,60,60,60,60,60
+60,60,60,60,60,60,60,0,10,3,60,60,60
+60,60,60,60,60,60,60,60,0,60,60,5,60
+60,60,60,60,60,60,60,60,6,0,18,10,60
+3,60,60,60,60,60,60,60,60,60,0,60,60
+60,60,60,60,60,60,60,60,60,60,60,0,4
+60,60,60,60,60,60,60,60,60,60,2,60,0
diff --git a/resources/test/instances/Constraint Propagation/Instance_Alain_140624/requests.dat b/resources/test/instances/Constraint Propagation/Instance_Alain_140624/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..db51b7095b91cbb387937376afde93182c57ea7f
--- /dev/null
+++ b/resources/test/instances/Constraint Propagation/Instance_Alain_140624/requests.dat	
@@ -0,0 +1,9 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
\ No newline at end of file
diff --git a/resources/test/instances/Constraint Propagation/Instance_Alain_140624_2/graph.dat b/resources/test/instances/Constraint Propagation/Instance_Alain_140624_2/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..b5de44afc15b80edd00d92ff8428a23f45147757
--- /dev/null
+++ b/resources/test/instances/Constraint Propagation/Instance_Alain_140624_2/graph.dat	
@@ -0,0 +1,30 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+,0,0
+,1,1
+,1,2
+,2,2
+,2,3
+,3,3
+,3,4
+,4,4
+,4,5
+,5,5
+,5,6
+,6,6
+,6,7
+#Matrix
+0,2,60,60,60,60,60,1,60,5,60,10,60
+60,0,15,6,60,60,60,60,60,60,60,60,60
+3,60,0,60,60,60,7,60,60,60,60,60,60
+60,60,60,0,9,7,60,60,60,60,60,60,60
+60,60,5,60,0,5,3,60,60,60,60,60,60
+60,60,60,60,4,0,5,60,60,60,60,60,60
+2,60,6,60,60,60,0,16,60,60,60,60,60
+60,60,60,60,60,60,60,0,10,3,60,60,60
+60,60,60,60,60,60,60,60,0,60,60,5,60
+60,60,60,60,60,60,60,60,6,0,18,10,60
+3,60,60,60,60,60,60,60,60,60,0,60,60
+60,60,60,60,60,60,60,60,60,60,60,0,4
+60,60,60,60,60,60,60,60,60,60,2,60,0
diff --git a/resources/test/instances/Constraint Propagation/Instance_Alain_140624_2/requests.dat b/resources/test/instances/Constraint Propagation/Instance_Alain_140624_2/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..734f753fffe159a892da8c80531ec721409ba554
--- /dev/null
+++ b/resources/test/instances/Constraint Propagation/Instance_Alain_140624_2/requests.dat	
@@ -0,0 +1,9 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+1,2,1,5,,,30,1
+3,4,10,12,,,15,1
+5,6,14,20,,,10,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
\ No newline at end of file
diff --git a/resources/test/instances/Constraint Propagation/basic_debug_instance/graph.dat b/resources/test/instances/Constraint Propagation/basic_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..033b4c0338c87919768a9a0b5eb0eb441fe40760
--- /dev/null
+++ b/resources/test/instances/Constraint Propagation/basic_debug_instance/graph.dat	
@@ -0,0 +1,16 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+residential,0,0
+residential,0,1
+work,1,0
+work,1,1
+#Matrix
+0,10,10,20
+10,0,20,10
+10,20,0,10
+20,10,10,0
+#PT line format : frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+15,340,560,1,5,1,2
+#Depot node index
+0
\ No newline at end of file
diff --git a/resources/test/instances/Constraint Propagation/basic_debug_instance/requests.dat b/resources/test/instances/Constraint Propagation/basic_debug_instance/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..9826720d6d886af414f7d6ff018ab94e32b81754
--- /dev/null
+++ b/resources/test/instances/Constraint Propagation/basic_debug_instance/requests.dat	
@@ -0,0 +1,6 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min,max,delta_time,capacity)
+0,3,510,540,30,1
+1,2,520,545,25,1
+#Expected result for end route D->R1.O->R2.O->R1.D->R2.D->D : [495;515],[505;520],[515;535],[525;545]
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/basic debug multimodal/graph.dat b/resources/test/instances/MultimodalHeuristic/basic debug multimodal/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..5d36ab989326ed29ebc028190a7be12300692f4c
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/basic debug multimodal/graph.dat	
@@ -0,0 +1,20 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+,0,0
+,8,4
+,1,1
+,3,3
+#Edges start,end,length
+0,1,5
+0,2,10
+0,3,20
+1,0,5
+1,3,15
+2,3,5
+3,0,20
+#PT line fixed
+1,2
+10,15
+20,25
+30,35
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/basic debug multimodal/requests.dat b/resources/test/instances/MultimodalHeuristic/basic debug multimodal/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..308a1af48d010e1155930ec69f051c754a0b3644
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/basic debug multimodal/requests.dat	
@@ -0,0 +1,4 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+0,3,10,30,40,60,,1
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/debug unfulfilled entry/graph.dat b/resources/test/instances/MultimodalHeuristic/debug unfulfilled entry/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..f2ab163b0bc517cd0139b1fbef0a28e5e0b06d71
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/debug unfulfilled entry/graph.dat	
@@ -0,0 +1,20 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+,0,0
+,8,4
+,1,1
+,3,3
+#Edges start,end,length
+0,1,60
+0,2,60
+0,3,20
+1,0,5
+1,3,15
+2,3,5
+3,0,20
+#PT line fixed
+1,2
+10,15
+20,25
+30,35
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/debug unfulfilled entry/requests.dat b/resources/test/instances/MultimodalHeuristic/debug unfulfilled entry/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..308a1af48d010e1155930ec69f051c754a0b3644
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/debug unfulfilled entry/requests.dat	
@@ -0,0 +1,4 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+0,3,10,30,40,60,,1
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/debug unfulfilled exit/graph.dat b/resources/test/instances/MultimodalHeuristic/debug unfulfilled exit/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..ea282ec307970bec38054c8567577de2df2d5a42
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/debug unfulfilled exit/graph.dat	
@@ -0,0 +1,19 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+,0,0
+,8,4
+,1,1
+,3,3
+#Edges start,end,length
+0,2,10
+0,3,20
+1,0,5
+1,3,15
+2,3,5
+3,0,20
+#PT line fixed
+1,2
+10,15
+20,25
+30,35
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/debug unfulfilled exit/requests.dat b/resources/test/instances/MultimodalHeuristic/debug unfulfilled exit/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..308a1af48d010e1155930ec69f051c754a0b3644
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/debug unfulfilled exit/requests.dat	
@@ -0,0 +1,4 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+0,3,10,30,40,60,,1
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/instance_alain_140624/even_more_requests.dat b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/even_more_requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..c1dac90104cd1a4ef73273766ce566459a4da400
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/even_more_requests.dat
@@ -0,0 +1,39 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
diff --git a/resources/test/instances/MultimodalHeuristic/instance_alain_140624/graph.dat b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..778e676bac985162b7f77f5ade43dc7aa73d068d
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/graph.dat
@@ -0,0 +1,41 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+,3,1
+,2,1
+,5,4
+,1,1
+,2,3
+,0,4
+,4,0
+,3,2
+,3,5
+,1,2
+,4,1
+,4,3
+,5,0
+,0,0
+,5,5
+,3,0
+#Edges start,end,length
+0,1,4
+1,3,3
+3,5,5
+5,4,2
+4,2,6
+2,6,7
+6,0,2
+0,7,1
+7,9,3
+9,8,6
+8,11,5
+11,12,4
+12,10,2
+10,0,3
+#PT line format : frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+5,2,40,1,5,1,3,5,6
+5,3,40,1,5,6,5,3,1
+3,6,40,1,5,7,8,2,12,10,6,0
+3,5,40,1,5,0,6,10,12,2,8,7
+4,9,40,1,5,14,3,11,9,0,15,6
+4,9,40,1,5,6,15,0,9,11,3,14
\ No newline at end of file
diff --git a/resources/test/instances/MultimodalHeuristic/instance_alain_140624/more_requests.dat b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/more_requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..99519c207525c7bfc9be58a4bef5d03dad5dcb7d
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/more_requests.dat
@@ -0,0 +1,15 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
diff --git a/resources/test/instances/MultimodalHeuristic/instance_alain_140624/requests.dat b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/requests.dat
new file mode 100644
index 0000000000000000000000000000000000000000..db51b7095b91cbb387937376afde93182c57ea7f
--- /dev/null
+++ b/resources/test/instances/MultimodalHeuristic/instance_alain_140624/requests.dat
@@ -0,0 +1,9 @@
+#Global Params (delta ratio)
+1
+#Requests (origin_idx,destination_idx,min_tw_start,max_tw_start,min_tw_end,max_tw_end,delta_time,capacity)
+1,2,3,6,,,16,1
+3,4,6,9,,,8,1
+5,6,11,13,,,18,1
+7,8,0,4,,,13,1
+9,10,2,7,,,22,1
+11,12,14,16,,,6,1
\ No newline at end of file
diff --git a/resources/test/instances/basic_debug_instance/PT_lines.csv b/resources/test/instances/PT Shortest Path/basic_debug_instance/PT_lines.csv
similarity index 100%
rename from resources/test/instances/basic_debug_instance/PT_lines.csv
rename to resources/test/instances/PT Shortest Path/basic_debug_instance/PT_lines.csv
diff --git a/resources/test/instances/basic_debug_instance/edges.csv b/resources/test/instances/PT Shortest Path/basic_debug_instance/edges.csv
similarity index 100%
rename from resources/test/instances/basic_debug_instance/edges.csv
rename to resources/test/instances/PT Shortest Path/basic_debug_instance/edges.csv
diff --git a/resources/test/instances/two_lines_debug_instance/graph.dat b/resources/test/instances/PT Shortest Path/basic_debug_instance/graph.dat
similarity index 58%
rename from resources/test/instances/two_lines_debug_instance/graph.dat
rename to resources/test/instances/PT Shortest Path/basic_debug_instance/graph.dat
index 813e885cc9def2e29ae9fb6a1c6f5829c8b8e67b..a3797f779dadc3be45e8eea1852b7b8b09d33410 100644
--- a/resources/test/instances/two_lines_debug_instance/graph.dat
+++ b/resources/test/instances/PT Shortest Path/basic_debug_instance/graph.dat	
@@ -17,6 +17,5 @@
 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
+#PT line format : frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+15,340,560,1,5,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/PT Shortest Path/basic_debug_instance/nodes.csv
similarity index 100%
rename from resources/test/instances/basic_debug_instance/nodes.csv
rename to resources/test/instances/PT Shortest Path/basic_debug_instance/nodes.csv
diff --git a/resources/test/instances/basic_debug_instance/params b/resources/test/instances/PT Shortest Path/basic_debug_instance/params
similarity index 100%
rename from resources/test/instances/basic_debug_instance/params
rename to resources/test/instances/PT Shortest Path/basic_debug_instance/params
diff --git a/resources/test/instances/PT Shortest Path/contiguous_lines_debug_instance/graph.dat b/resources/test/instances/PT Shortest Path/contiguous_lines_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..81c2e0ebbc5bb02d28392fa4efe75089a88b469c
--- /dev/null
+++ b/resources/test/instances/PT Shortest Path/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 : frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+15,340,560,1,5,3,4,5,6
+15,340,560,1,5,6,5,4,3
+30,360,580,1,5,2,3,4,5,6,7
+30,240,580,1,5,7,6,5,4,3,2
\ No newline at end of file
diff --git a/resources/test/instances/basic_debug_instance/graph.dat b/resources/test/instances/PT Shortest Path/cycling_line_debug_instance/graph.dat
similarity index 89%
rename from resources/test/instances/basic_debug_instance/graph.dat
rename to resources/test/instances/PT Shortest Path/cycling_line_debug_instance/graph.dat
index 97764d4b1618d78d86986f38f8319ce70b1d9d9f..5b703a3f869046fcda88f2e09b1cdeb24abe25df 100644
--- a/resources/test/instances/basic_debug_instance/graph.dat
+++ b/resources/test/instances/PT Shortest Path/cycling_line_debug_instance/graph.dat	
@@ -18,4 +18,4 @@
 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
+30,240,580,1,5,7,6,7,5,4,3,2,7
\ No newline at end of file
diff --git a/resources/test/instances/contiguous_lines_debug_instance/graph.dat b/resources/test/instances/PT Shortest Path/multiple_crossing_lines_debug_instance/graph.dat
similarity index 63%
rename from resources/test/instances/contiguous_lines_debug_instance/graph.dat
rename to resources/test/instances/PT Shortest Path/multiple_crossing_lines_debug_instance/graph.dat
index 67e8ad8b90a0df3d0b1cece891aeefd48d2b677e..c023ffff2aad2cec480100de16752755eaffd9ae 100644
--- a/resources/test/instances/contiguous_lines_debug_instance/graph.dat
+++ b/resources/test/instances/PT Shortest Path/multiple_crossing_lines_debug_instance/graph.dat	
@@ -18,7 +18,9 @@
 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
+15,340,560,1,5,3,4,5,6
+15,340,560,1,5,6,5,4,3
+30,360,580,1,5,2,3,4,5,6,7
+30,240,580,1,5,7,6,5,4,3,2
+30,290,580,1,5,0,4,1
+30,290,580,1,5,1,4,0
\ No newline at end of file
diff --git a/resources/test/instances/cycling_line_debug_instance/graph.dat b/resources/test/instances/PT Shortest Path/multiple_cycling_lines_debug_instance/graph.dat
similarity index 57%
rename from resources/test/instances/cycling_line_debug_instance/graph.dat
rename to resources/test/instances/PT Shortest Path/multiple_cycling_lines_debug_instance/graph.dat
index 5e6abe119b0ab7da31d6ccb70cbe31df50740e35..05ce249908f0d60f98d78f369016a97202db42b7 100644
--- a/resources/test/instances/cycling_line_debug_instance/graph.dat
+++ b/resources/test/instances/PT Shortest Path/multiple_cycling_lines_debug_instance/graph.dat	
@@ -18,4 +18,10 @@
 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
+15,340,560,1,5,3,4,5,6,3
+15,340,560,1,5,6,5,4,3,6
+30,360,580,1,5,2,3,4,5,6,7,2
+30,240,580,1,5,7,6,5,4,3,2,7
+30,240,580,1,5,7,6,7,5,4,3,2,7
+30,290,580,1,5,0,4,1,0
+30,290,580,1,5,1,4,0,1
\ No newline at end of file
diff --git a/resources/test/instances/PT Shortest Path/single_node_lines_debug_instance/graph.dat b/resources/test/instances/PT Shortest Path/single_node_lines_debug_instance/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..5c0c7159cba26a252341b8be4f177690943baad5
--- /dev/null
+++ b/resources/test/instances/PT Shortest Path/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 : frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+15,340,560,1,5,3,3,3,3,3,3,3,3,3
+15,340,560,1,5,7,7,7,7,7,7,7,7
+30,360,580,1,5,2,3,4,5,6,7
\ No newline at end of file
diff --git a/resources/test/instances/single_node_lines_debug_instance/graph.dat b/resources/test/instances/PT Shortest Path/two_lines_debug_instance/graph.dat
similarity index 53%
rename from resources/test/instances/single_node_lines_debug_instance/graph.dat
rename to resources/test/instances/PT Shortest Path/two_lines_debug_instance/graph.dat
index 971382ad792e295ece30769a3cbe3a436be5c70a..7b7103b86201eacea8b25cbff7f118985ff8a85b 100644
--- a/resources/test/instances/single_node_lines_debug_instance/graph.dat
+++ b/resources/test/instances/PT Shortest Path/two_lines_debug_instance/graph.dat	
@@ -17,7 +17,6 @@
 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
+#PT line format : frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+15,340,560,1,5,3,4,5,6
+30,360,580,1,5,0,1,2,3,7
\ No newline at end of file
diff --git a/resources/test/instances/graph preprocess/book example/graph.dat b/resources/test/instances/graph preprocess/book example/graph.dat
new file mode 100644
index 0000000000000000000000000000000000000000..1969ed8fd4325010dec5db064a9a7b4e05118635
--- /dev/null
+++ b/resources/test/instances/graph preprocess/book example/graph.dat	
@@ -0,0 +1,18 @@
+#Params (seed)
+123456789
+#Nodes format : status (work, leisure, residential),x,y
+residential,0,0
+residential,0,1
+work,1,1
+work,2,0
+work,2,1
+#Matrix
+0,10,5,-1,-1
+-1,0,2,1,-1
+-1,3,0,-1,2
+-1,-1,-1,0,4
+7,-1,-1,6,0
+#PT line format : frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+15,340,560,1,5,1,2
+#Depot node index
+0
\ 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
deleted file mode 100644
index 4cdc53d46a0ce446b94b1fc38095bc17da27a63f..0000000000000000000000000000000000000000
--- a/resources/test/instances/multiple_crossing_lines_debug_instance/graph.dat
+++ /dev/null
@@ -1,26 +0,0 @@
-#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
deleted file mode 100644
index 6b69917d8c38621cb98042260a55f1ca8514ebad..0000000000000000000000000000000000000000
--- a/resources/test/instances/multiple_cycling_lines_debug_instance/graph.dat
+++ /dev/null
@@ -1,27 +0,0 @@
-#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/src/ShortestPath/ShortestPathContainer.h b/src/ShortestPath/ShortestPathContainer.h
deleted file mode 100644
index 3a3b9a54f06761b4bd642507682b95737c418d3d..0000000000000000000000000000000000000000
--- a/src/ShortestPath/ShortestPathContainer.h
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// 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
deleted file mode 100644
index d5eec8acb4ca0c2d2ec8933e00a91ae4d4bd634e..0000000000000000000000000000000000000000
--- a/src/ShortestPath/TimeDependentShortestPathContainer.h
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// 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/TransitShortestPathContainer.cpp b/src/ShortestPath/Transit/TransitShortestPathContainer.cpp
deleted file mode 100644
index bee8ce44b11ed7015d414e6a774d9e77802dac4f..0000000000000000000000000000000000000000
--- a/src/ShortestPath/Transit/TransitShortestPathContainer.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-//
-// 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
deleted file mode 100644
index 55166e4c813dee05d7e72396e07c24d81092369f..0000000000000000000000000000000000000000
--- a/src/ShortestPath/Transit/TransitShortestPathContainer.h
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// 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/TimeWindow.cpp b/src/TimeWindow.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c04d424a485ce8e1469ee7169b4ba87ee62924e1
--- /dev/null
+++ b/src/TimeWindow.cpp
@@ -0,0 +1,58 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#include "TimeWindow.h"
+
+
+TimeWindow TimeWindow::operator+(uint rhs) const {
+    return TimeWindow(this->min + rhs, this->max + rhs);
+}
+
+TimeWindow& TimeWindow::operator+=(uint rhs) {
+    this->min += rhs;
+    this->max += rhs;
+    return *this;
+}
+
+TimeWindow TimeWindow::operator-(uint rhs) const {
+    return TimeWindow(this->min - rhs, this->max - rhs);
+}
+
+TimeWindow& TimeWindow::operator-=(uint rhs) {
+    this->min -= rhs;
+    this->max -= rhs;
+    return *this;
+}
+
+TimeWindow TimeWindow::operator+(const TimeWindow& rhs) const {
+    return TimeWindow(this->min + rhs.min, this->max + rhs.max);
+}
+
+TimeWindow& TimeWindow::operator+=(const TimeWindow& rhs) {
+    this->min += rhs.min;
+    this->max += rhs.max;
+    return *this;
+}
+
+TimeWindow TimeWindow::operator-(const TimeWindow& rhs) const {
+    return TimeWindow(this->min - rhs.min, this->max - rhs.max);
+}
+
+TimeWindow& TimeWindow::operator-=(const TimeWindow& rhs) {
+    this->min -= rhs.min;
+    this->max -= rhs.max;
+    return *this;
+}
+
+bool TimeWindow::isInTimeWindow(uint instant) const {
+    return this->min <= instant && this->max >= instant;
+}
+
+bool TimeWindow::isInTimeWindow(const TimeWindow &instant) const {
+    return this->isInTimeWindow(instant.min) && this->isInTimeWindow(instant.max);
+}
+
+std::string TimeWindow::to_string() const {
+    return "[" + std::to_string(min) + "," + std::to_string(max) + "]";
+}
diff --git a/src/TimeWindow.h b/src/TimeWindow.h
new file mode 100644
index 0000000000000000000000000000000000000000..277b9621ce4e3712dcdf55cd5074ae6d9ba31ff4
--- /dev/null
+++ b/src/TimeWindow.h
@@ -0,0 +1,47 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#ifndef GREEDYALGORITHM_TIMEWINDOW_H
+#define GREEDYALGORITHM_TIMEWINDOW_H
+
+#include <cstdlib>
+#include <string>
+#include "utils/Globals.h"
+
+/**
+ * Represents [min, max] time windows with appropriate operators for TW operations (+,-,isInTimeWindow).
+ * Prefer using +=/-= notations to modify in place, as + and - operators return a new TimeWindow object.
+ */
+struct TimeWindow {
+    uint min = 0, max = 0;
+    TimeWindow() = default;
+    TimeWindow(uint min, uint max) : min(min), max(max) {}
+    /**
+     * Checks if the given instant is inside the [min,max] interval
+     * @return True iff instant is in the time window, false otherwise
+     */
+    [[nodiscard]] bool isInTimeWindow(uint instant) const;
+    /**
+    * Checks if the given time window is contained inside the [min,max] interval of this
+    * @return True iff instant is in the time window, false otherwise
+    */
+    [[nodiscard]] bool isInTimeWindow(const TimeWindow& instant) const;
+
+    [[nodiscard]] std::string to_string() const;
+
+    TimeWindow operator+(uint rhs) const;
+    TimeWindow& operator+=(uint rhs);
+    TimeWindow operator-(uint rhs) const;
+    TimeWindow& operator-=(uint rhs);
+    TimeWindow operator+(const TimeWindow& rhs) const;
+    TimeWindow& operator+=(const TimeWindow& rhs);
+    TimeWindow operator-(const TimeWindow& rhs) const;
+    TimeWindow& operator-=(const TimeWindow& rhs);
+    TimeWindow& operator=(const TimeWindow& rhs) = default;
+
+    struct invalid_time_window_exception : std::exception {};
+};
+
+
+#endif //GREEDYALGORITHM_TIMEWINDOW_H
diff --git a/src/algorithm/DARP/Heuristics/BestInsertionHeuristic.cpp b/src/algorithm/DARP/Heuristics/BestInsertionHeuristic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..db0e8336f3c87679a3e217d3693c935ab2c5d13b
--- /dev/null
+++ b/src/algorithm/DARP/Heuristics/BestInsertionHeuristic.cpp
@@ -0,0 +1,73 @@
+//
+// Created by romain on 20/06/24.
+//
+
+#include "BestInsertionHeuristic.h"
+
+#ifdef DEBUG_BEST_INSERTION_HEURISTIC
+#include <iostream>
+#define DEBUG_BIH_MSG(str) do { std::cout << "[BEST INSERTION] " << str << std::endl; } while( false )
+#else
+#define DEBUG_BIH_MSG(str) do { } while ( false )
+#endif
+
+SAEVRouteChangelist BestInsertionHeuristic::tryVehicleBestInsertion(SAEVKeyPoint &requestKp, size_t vehicleId, SAEVRoute& route) {
+    BestInsertionQueue bestInsertionsQueue = route.getBestFeasibleInsertionsQueue(requestKp, vehicleId);
+    return tryVehicleBestInsertion(bestInsertionsQueue, route);
+}
+
+SAEVRouteChangelist BestInsertionHeuristic::tryVehicleBestInsertion(BestInsertionQueue& bestInsertionsQueue, SAEVRoute& route) {
+    bool bestInsertionFound = false;
+    BestRequestInsertion currentBestInsertion;
+    SAEVKeyPoint & requestKp = bestInsertionsQueue.getOriginKp();
+    SAEVRouteChangelist lastInsertionChangelist(&route, &requestKp);
+
+    while(!bestInsertionsQueue.empty() && !bestInsertionFound) {
+        currentBestInsertion = bestInsertionsQueue.topAndPop();
+        DEBUG_BIH_MSG("Trying insertion " + currentBestInsertion.to_string() + ", remaining : " + std::to_string(bestInsertionsQueue.size()));
+        lastInsertionChangelist = route.tryAddRequest(requestKp,currentBestInsertion.getOriginInsertionKp(),currentBestInsertion.getDestinationInsertionKp());
+        //If insertion worked, signal it, otherwise revert changes
+        if(lastInsertionChangelist.getStatus() == SAEVRouteChangelist::InsertionStatus::SUCCESS) {
+            DEBUG_BIH_MSG("\tBest valid insertion found !\n\t\t" + currentBestInsertion.to_string());
+            bestInsertionFound = true;
+        } else {
+            DEBUG_BIH_MSG("Failed to insert best insertion " + currentBestInsertion.to_string());
+            lastInsertionChangelist.revertChanges();
+        }
+    }
+
+    return lastInsertionChangelist;
+}
+
+size_t BestInsertionHeuristic::doBestRequestInsertionForRoute(SAEVKeyPoint &requestKp, SAEVRoute& route) {
+    size_t vehicleId = 0;
+    bool insertionSuccess{false};
+    //Iteratively try inserting in every active vehicle and the first inactive vehicle
+    do { //TODO: improve this to mutualize best insertions amongst best vehicles ?
+        insertionSuccess = tryVehicleBestInsertion(requestKp, vehicleId, route).success();
+    } while(!insertionSuccess && ++vehicleId <= route.getLastActiveVehicleId() + 1);
+
+    //Update last active vehicle ID
+    if(vehicleId > route.getLastActiveVehicleId()) {
+        route.insertRequestInNewVehicle(requestKp);
+    }
+
+    return vehicleId;
+}
+
+SAEVRouteChangelist BestInsertionHeuristic::tryBestRequestInsertionInActiveVehicle(SAEVKeyPoint &requestKp, SAEVRoute &route) {
+    if(requestKp.getNodeIndex() == requestKp.getCounterpart()->getNodeIndex()) {
+        SAEVRouteChangelist res(&route, &requestKp);
+        res.setStatus(SAEVRouteChangelist::InsertionStatus::SUCCESS);
+        return res;
+    } else {
+        size_t vehicleId = 0;
+        BestInsertionQueue bestInsertions{requestKp};
+        //Iteratively try inserting in every active vehicle and the first inactive vehicle
+        do {
+            route.getBestFeasibleInsertionsQueue(bestInsertions, requestKp, vehicleId);
+        } while(++vehicleId <= route.getLastActiveVehicleId());
+
+        return tryVehicleBestInsertion(bestInsertions, route);
+    }
+}
diff --git a/src/algorithm/DARP/Heuristics/BestInsertionHeuristic.h b/src/algorithm/DARP/Heuristics/BestInsertionHeuristic.h
new file mode 100644
index 0000000000000000000000000000000000000000..05d4a2359f0691cd5113c12ebdd6057c1ba80f11
--- /dev/null
+++ b/src/algorithm/DARP/Heuristics/BestInsertionHeuristic.h
@@ -0,0 +1,54 @@
+//
+// Created by romain on 20/06/24.
+//
+
+#ifndef GREEDYALGORITHM_BESTINSERTIONHEURISTIC_H
+#define GREEDYALGORITHM_BESTINSERTIONHEURISTIC_H
+
+
+#include <cstdlib>
+#include "../../../routes/vehicle/SAEVRoute.h"
+
+class BestInsertionHeuristic {
+public:
+    /**
+     * Automatically inserts the given request in a vehicle, potentially creating a new one if no active vehicle works
+     * @param requestKp ID of the request to insert in the route
+     * @param route the route structure in which the request will be inserted
+     * @return ID of the vehicle in which the request has been
+     */
+    static size_t doBestRequestInsertionForRoute(SAEVKeyPoint &requestKp, SAEVRoute& route);
+    /**
+     * Iteratively attempts insertions in the currently active vehicles in two steps :
+     * 1) creates a global best insertion list for all active vehicles
+     * 2) tries every insertions from best to worst
+     * 3) return the ID of the vehicle in which the request was inserted if an active vehicle was viable, return the most favourable inactive vehicle's ID otherwise
+     * @param requestKp ID of the request to insert in the route
+     * @param route the route structure in which the request will be inserted
+     * @return The ID of the active vehicle in which our request was inserted, or the ID of the most favourable inactive vehicle
+     */
+    static SAEVRouteChangelist tryBestRequestInsertionInActiveVehicle(SAEVKeyPoint &requestKp, SAEVRoute& route);
+    /**
+     * Iteratively tests best insertions wrt scoring function (detour) in the given vehicle and route
+     * @param requestKp ID of the request to insert in the vehicle
+     * @param requestKp ID of the vehicle in which to insert the vehicle
+     * @param route
+     * @return true iff the request was inserted in the vehicle, false if no best insertion yielded a possible insertion
+     */
+    static SAEVRouteChangelist tryVehicleBestInsertion(SAEVKeyPoint &requestKp, size_t vehicleId, SAEVRoute& route);
+    /**
+     * Tries the best insertions
+     * @param bestInsertionsQueue
+     * @param route
+     * @return
+     */
+    static SAEVRouteChangelist tryVehicleBestInsertion(BestInsertionQueue& bestInsertionsQueue, SAEVRoute& route);
+
+
+    /** TODO Implement those to prevent trying every single best insertion
+    static bool vehicle_K_BestInsertion(size_t requestId, size_t vehicleId, SAEVRoute route);
+    static bool vehicleScoreThresholdBestInsertion(size_t requestId, size_t vehicleId, SAEVRoute& route); */
+};
+
+
+#endif //GREEDYALGORITHM_BESTINSERTIONHEURISTIC_H
diff --git a/src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp b/src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..df5882d2f16fd36412f2ba5d2f24f8fd62eddc5d
--- /dev/null
+++ b/src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp
@@ -0,0 +1,305 @@
+//
+// Created by romain on 15/07/24.
+//
+
+#include "MultimodalModularHeuristic.h"
+
+using transit_order_function = std::function<bool(MultimodalModularHeuristic::ScoredTransitAccess, MultimodalModularHeuristic::ScoredTransitAccess)>;
+
+std::vector<TransitAccess> MultimodalModularHeuristic::getBestTransitEntriesList(const Request &baseRequest) const {
+    const auto& bestStationsIndexVector = _graph->getNode(
+            baseRequest.getOriginNodeIndex()).getBestStationsNodeIdxVector();
+    std::vector<TransitAccess> results; //init results vector to the appropriate size
+    results.reserve(std::min(Constants::MAX_TRANSIT_ENTRY_CANDIDATES, bestStationsIndexVector.size()));
+    //Iterate over the best stations saved prior
+    for(const auto& bestStationNodeIdx : bestStationsIndexVector) {
+        int maxDepartureTime = -1;
+
+        //Iterate over the lines available on the node selected
+        for(const Node& bestStationNode = _graph->getNode(bestStationNodeIdx);
+            const auto& lineStop : bestStationNode.getPTLinesSet()) {
+            //Find the next passage lower or equal to our max entry time constraint
+            auto iterator = lineStop.findNextScheduledPassage(getMaxEntryConstraint(baseRequest, bestStationNodeIdx));
+            if(iterator != lineStop.getSchedule().cbegin()) { //Iterator is invalid if it points to schedule beginning (no result lower than our constraint)
+                --iterator; //Move iterator to the value that's under our max entry constraint
+                if(*iterator > maxDepartureTime //If we've found a valid time that's superior to our current max time
+                && *iterator > getMinEntryConstraint(baseRequest, bestStationNodeIdx)) { //and respects min entry time, replace old value
+                    maxDepartureTime = *iterator;
+                }
+            }
+        }
+
+        //If we've found a valid max departure time for this station, add it to the list
+        if(maxDepartureTime > -1) {
+            results.emplace_back(bestStationNodeIdx, maxDepartureTime);
+            if(results.size() == Constants::MAX_TRANSIT_ENTRY_CANDIDATES) {
+                break;
+            }
+        }
+    }
+    return results;
+}
+
+uint MultimodalModularHeuristic::getMinEntryConstraint(const Request &request, size_t ptEntryNodeIdx) const {
+    return request.getMinDepartureTw() + _graph->getShortestSAEVPath(request.getOriginNodeIndex(), ptEntryNodeIdx);
+}
+
+uint MultimodalModularHeuristic::getMaxEntryConstraint(const Request &request, size_t ptEntryNodeIdx) const {
+    return request.getMaxArrivalTw() - ((uint) std::floor(_graph->getShortestSAEVPath(ptEntryNodeIdx, request.getDestinationNodeIndex()) * request.getTransitTravelTimeRatio()));
+}
+
+/**
+ * Generates the best entries list (ordered by an implementation-dependant ,
+ * then generates a vector of requests from these entries before starting the entry subrequest insertion process <br>
+ * The insertion process first tries best insertions without creating a new vehicles in order of the best entries list.
+ * If none result in a valid insertion, we insert the first subrequest (supposedly the better one) in a new vehicle
+ * @param baseRequest const ref to the request we use as our base to get the best entries list
+ * @param baseRequestId ID/index in the request vector for our base request
+ * @return The subrequest successfully inserted in our route. This method's caller needs to add this request to its main request vector
+ */
+const Request & MultimodalModularHeuristic::insertBestTransitEntryInRoute(const Request &baseRequest, size_t baseRequestId) {
+    std::vector<TransitAccess> entriesAccessList = getBestTransitEntriesList(baseRequest);
+    return insertBestTransitAccessInRoute(baseRequest, entriesAccessList, baseRequestId, true);
+}
+
+/**
+ * Generates a vector of requests from the given access list before starting the entry subrequest insertion process <br>
+ * The insertion process first tries best insertions without creating a new vehicles in order of the best entries list.
+ * If none result in a valid insertion, we insert the first subrequest (supposedly the better one) in a new vehicle
+ * @param baseRequest const ref to the request we use as our base to generate subrequests from the sorted best access list
+ * @param transitAccessList A list of best entries, preferably ordered from best to worst and sized according to the max number of candidates we want to try inserting
+ * @param baseRequestId ID/index in the request vector for our base request
+ * @return The subrequest successfully inserted in our route. This method's caller needs to add this request to its main request vector
+ */
+const Request &
+MultimodalModularHeuristic::insertBestTransitAccessInRoute(const Request &baseRequest,
+                                                       const std::vector<TransitAccess> &transitAccessList,
+                                                       size_t baseRequestId, bool isEntry) {
+    std::vector<Request> accessSubRequestsList;
+    size_t maxSize = isEntry ? Constants::MAX_TRANSIT_ENTRY_CANDIDATES : Constants::MAX_TRANSIT_EXIT_CANDIDATES;
+    accessSubRequestsList.reserve(maxSize); //Init entry subrequests list to the appropriate size
+    //Generate subrequests from best transit entries
+    for(auto const& access : transitAccessList) {
+        try {
+            if(isEntry)
+                accessSubRequestsList.emplace_back(*_graph, baseRequest, access);
+            else
+                accessSubRequestsList.emplace_back(*_graph, baseRequest, access,
+                                                   _route->getEntrySubRequestOrigin(baseRequestId));
+        }
+        catch(const TimeWindow::invalid_time_window_exception& e) {
+            DEBUG_MMH_MSG("Invalid Time Window during candidate sub request creation, it won't be added to the list");
+        }
+    }
+    return insertBestTransitAccessInRoute(accessSubRequestsList, baseRequestId, isEntry);
+}
+
+/**
+ * The insertion process first tries best insertions without creating a new vehicles in order of the best entries list. <br>
+ * If none result in a valid insertion, we insert the first subrequest (supposedly the better one) in a new vehicle. <br>
+ * While doing these insertions, the route and global request vector is updated with the appropriate request so that no inconsistencies in data structures happen
+ * @param accessSubRequestsList A list of entry subrequest candidates, preferably ordered from best to worst candidate, but the list order is implementation dependant
+ * @param baseRequestId ID/index in the request vector for our base request
+ * @param isEntry true iff the given access requests are transit entry requests
+ * @return The subrequest successfully inserted in our route
+ */
+const Request &
+MultimodalModularHeuristic::insertBestTransitAccessInRoute(const std::vector<Request> &accessSubRequestsList,
+                                                       size_t baseRequestId, bool isEntry) {
+    for(const auto& subreq : accessSubRequestsList) {
+        updateSubRequest(baseRequestId, subreq, isEntry);
+        SAEVRouteChangelist changeList = BestInsertionHeuristic::tryBestRequestInsertionInActiveVehicle(
+                _route->getSubRequestOrigin(baseRequestId, isEntry), *_route);
+        //If we've found an insertion that doesn't create a vehicle, stop there
+        if(changeList.success()) {
+            DEBUG_MMH_MSG(std::string(isEntry ? "ENTRY" : "EXIT") + " CANDIDATE SUCCESS : " + subreq.to_string());
+            return getSubrequest(baseRequestId, isEntry);
+        } else {
+            DEBUG_MMH_MSG(std::string(isEntry ? "ENTRY" : "EXIT") + " CANDIDATE FAILURE : " + subreq.to_string() + "\n"
+                        + "CAUSE : " + changeList.getStatusString());
+        }
+    }
+
+    // If no valid candidate was given, still create a fake
+    // subrequest and mention failure in the appropriate vector
+    if(accessSubRequestsList.empty()) {
+        DEBUG_MMH_MSG(std::string("UNFULFILLED SUBREQUEST ") + (isEntry ? "ENTRY" : "EXIT"));
+        updateSubRequest(baseRequestId, (*_requestsVect)[baseRequestId], isEntry);
+        updateUnfulfilledSubrequest(baseRequestId, isEntry, true); //mark failures for debug/statistics
+        if(!isEntry) { //Exit subrequest : remove entry subrequest and add baseRequest to the "unfulfilled list"
+            _route->removeRequestWithPropagation(baseRequestId, true);
+        }
+        emplaceDARPRequest(baseRequestId);
+    } else {
+        // If no active vehicle insertion worked, do best insertion on a new vehicle
+        // with the first subrequest (supposedly it's the most advantageous)
+        DEBUG_MMH_MSG("CREATE VEHICLE");
+        updateSubRequest(baseRequestId, accessSubRequestsList[0], isEntry);
+        _route->insertRequestInNewVehicle(_route->getExitSubRequestOrigin(baseRequestId));
+    }
+
+    return getSubrequest(baseRequestId, isEntry);
+}
+
+const Graph *MultimodalModularHeuristic::getGraph() const {
+    return _graph;
+}
+
+void MultimodalModularHeuristic::setGraph(const Graph *graph) {
+    _graph = graph;
+}
+
+SAEVRoute *MultimodalModularHeuristic::getRoute() const {
+    return _route;
+}
+
+void MultimodalModularHeuristic::setRoute(SAEVRoute *route) {
+    _route = route;
+}
+
+void MultimodalModularHeuristic::updateSubRequest(size_t requestId, const Request &request, bool isEntry) {
+    if(isEntry)
+        _entrySubRequests[requestId] = request;
+    else
+        _exitSubRequests[requestId] = request;
+
+    if(isEntry) {
+        _route->getEntrySubRequestOrigin(requestId).setRequest(&getSubrequest(requestId, isEntry));
+        _route->getEntrySubRequestDestination(requestId).setRequest(&getSubrequest(requestId, isEntry));
+    } else {
+        _route->getExitSubRequestOrigin(requestId).setRequest(&getSubrequest(requestId, isEntry));
+        _route->getExitSubRequestDestination(requestId).setRequest(&getSubrequest(requestId, isEntry));
+    }
+}
+
+const Request& MultimodalModularHeuristic::getSubrequest(size_t requestId, bool isEntry) {
+    return isEntry ? _entrySubRequests[requestId] : _exitSubRequests[requestId];
+}
+
+double MultimodalModularHeuristic::getTransitExitScore(const Request &baseRequest, const TransitAccess &exitData) const {
+    return getTransitExitScore(exitData.getAccessNodeIdx(), baseRequest.getDestinationNodeIndex(), exitData.getAccessTimestamp());
+}
+
+double MultimodalModularHeuristic::getTransitExitScore(size_t transitExitNodeIndex, size_t requestDestinationNodeIndex, uint transitExitTimestamp) const {
+    return _graph->getShortestSAEVPath(transitExitNodeIndex, requestDestinationNodeIndex) + transitExitTimestamp;
+}
+
+transit_order_function
+MultimodalModularHeuristic::getScoredTransitExitOrderer() {
+    return [](MultimodalModularHeuristic::ScoredTransitAccess lhs, MultimodalModularHeuristic::ScoredTransitAccess rhs) { return lhs.score < rhs.score; };
+}
+
+uint MultimodalModularHeuristic::getMinExitConstraint(size_t baseRequestId, const SAEVKeyPoint &entrySubRequestOriginKP,
+                                                  size_t transitExitNodeIdx) const {
+    return 0;
+}
+
+uint MultimodalModularHeuristic::getMaxExitConstraint(size_t baseRequestId, const SAEVKeyPoint &entrySubRequestOriginKP,
+                                                  size_t transitExitNodeIdx) const {
+    const Request& baseRequest = (*_requestsVect)[baseRequestId];
+    return (entrySubRequestOriginKP.getMinTw() + baseRequest.getDeltaTime()) -
+           _graph->getShortestSAEVPath(transitExitNodeIdx, baseRequest.getDestinationNodeIndex());
+}
+
+std::vector<TransitAccess>
+MultimodalModularHeuristic::getBestTransitExitsList(size_t baseRequestId) {
+    const Request& baseRequest = (*_requestsVect)[baseRequestId];
+    const SAEVKeyPoint& entrySubRequestOriginKP = _route->getEntrySubRequestOrigin(baseRequestId);
+    return getBestTransitExitsList(baseRequestId, baseRequest, entrySubRequestOriginKP);
+}
+
+std::vector<TransitAccess>
+MultimodalModularHeuristic::getBestTransitExitsList(size_t baseRequestId, const Request &baseRequest,
+                                                const SAEVKeyPoint &entrySubRequestOriginKP) const {
+    std::vector<MultimodalModularHeuristic::ScoredTransitAccess> scoredTransitExits;
+    //Get departure time/shortest transit paths list from the entry sub request's max time (this means we take the first transit available after current max arrival)
+    //TODO : study other approaches (e.g check for a faster max arrival if it's valid and allows better paths. This would require propagation => costly)
+
+    size_t entryNodeIdx = entrySubRequestOriginKP.getCounterpart()->getNodeIndex();
+    uint transitMinDepartureTime = entrySubRequestOriginKP.getCounterpart()->getMaxTw();
+    if(entrySubRequestOriginKP.getNodeIndex() == entrySubRequestOriginKP.getCounterpart()->getNodeIndex()) {
+        transitMinDepartureTime = baseRequest.getMinDepartureTw(); //TODO: check with the team if this choice is fine
+    }
+    const auto& [departureTime, shortestTransitPaths] = _graph->getShortestTransitPathsFrom(entryNodeIdx,
+                                                                                            transitMinDepartureTime);
+
+    //Iterate over the best stations saved prior
+    for(const auto& shortestTransitPath : shortestTransitPaths) {
+        //Check valid transit path + arrival node != starting point
+        if(shortestTransitPath.getArrivalTime() >= 0 && shortestTransitPath.getArrivalNode() != entryNodeIdx
+        && shortestTransitPath.getArrivalTime() <= getMaxExitConstraint(baseRequestId, entrySubRequestOriginKP, shortestTransitPath.getArrivalNode())) {
+            TransitAccess exit{shortestTransitPath.getArrivalNode(),  (uint) shortestTransitPath.getArrivalTime()};
+            scoredTransitExits.emplace_back(exit, getTransitExitScore(baseRequest, exit));
+        }
+    }
+
+    //Sort and truncate transit exits list while removing score data that's unnecessary in later steps
+    std::ranges::sort(scoredTransitExits, getScoredTransitExitOrderer());
+    uint finalVectorSize = std::min(scoredTransitExits.size(), Constants::MAX_TRANSIT_EXIT_CANDIDATES);
+    std::vector<TransitAccess> truncatedTransitExitsList{scoredTransitExits.begin(), scoredTransitExits.begin() + finalVectorSize};
+    return truncatedTransitExitsList;
+}
+
+const Request & MultimodalModularHeuristic::insertBestTransitExitsInRoute(const Request &baseRequest, size_t baseRequestId) {
+    std::vector<TransitAccess> exitAccessList = getBestTransitExitsList(baseRequestId);
+    return insertBestTransitAccessInRoute(baseRequest, exitAccessList, baseRequestId, false);
+}
+
+void MultimodalModularHeuristic::updateUnfulfilledSubrequest(size_t baseRequestId, bool isEntry, bool value) {
+    if(isEntry) {
+        _unfulfilledTransitEntry[baseRequestId] = value;
+    } else {
+        _unfulfilledTransitExit[baseRequestId] = value;
+    }
+}
+
+const std::vector<bool> &MultimodalModularHeuristic::getUnfulfilledTransitExit() const {
+    return _unfulfilledTransitExit;
+}
+
+const std::vector<bool> &MultimodalModularHeuristic::getUnfulfilledTransitEntry() const {
+    return _unfulfilledTransitEntry;
+}
+
+bool MultimodalModularHeuristic::isEntryFulfilled(size_t baseRequestId) {
+    return !_unfulfilledTransitEntry[baseRequestId];
+}
+
+const std::vector<Request> &MultimodalModularHeuristic::getEntrySubRequests() const {
+    return _entrySubRequests;
+}
+
+const std::vector<Request> &MultimodalModularHeuristic::getExitSubRequests() const {
+    return _exitSubRequests;
+}
+
+void MultimodalModularHeuristic::emplaceDARPRequest(size_t baseRequestId) {
+    _darpRequestList.emplace_back(baseRequestId);
+}
+
+void MultimodalModularHeuristic::doMultimodalRequestsInsertion() {
+    // Step 1
+    // Execute transit entry insertion procedure first, it'll mark every failed request
+    // and greedily insert the best compatible entry request
+    for(size_t requestId = 0; requestId < _requestsVect->size(); ++requestId) {
+        const Request& baseRequest = (*_requestsVect)[requestId];
+        if(baseRequest.isMultimodal())
+            insertBestTransitEntryInRoute(baseRequest, requestId);
+        else
+            emplaceDARPRequest(requestId);
+    }
+
+    // Step 2
+    // If the entry request was fulfilled, do the exit insertion procedure
+    // if this step fails (no compatible exit) undoes the entry insertion, which is a costly process
+    for(size_t requestId = 0; requestId < _requestsVect->size(); ++requestId) {
+        if(!_unfulfilledTransitEntry[requestId])
+            insertBestTransitExitsInRoute((*_requestsVect)[requestId], requestId);
+    }
+
+    // Step 3
+    // Do best request insertion for every unfulfilled multimodal request and non-multimodal request
+    for(size_t requestId : _darpRequestList) {
+        BestInsertionHeuristic::doBestRequestInsertionForRoute(_route->getRequestOrigin(requestId), *_route);
+    }
+}
diff --git a/src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h b/src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h
new file mode 100644
index 0000000000000000000000000000000000000000..46242b3c269c35526f4b388948f6204b80aee5e7
--- /dev/null
+++ b/src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h
@@ -0,0 +1,235 @@
+//
+// Created by romain on 15/07/24.
+//
+
+#ifndef GREEDYALGORITHM_MULTIMODALMODULARHEURISTIC_H
+#define GREEDYALGORITHM_MULTIMODALMODULARHEURISTIC_H
+
+
+#include <cstddef>
+#include <vector>
+#include <float.h>
+#include "../../../instance/requests/Request.h"
+#include "../../../routes/vehicle/SAEVRoute.h"
+#include "TransitAccess.h"
+#include "../../DARP/Heuristics/BestInsertionHeuristic.h"
+#include "../../../../test/lib/googletest/googletest/include/gtest/gtest_prod.h"
+
+#ifdef DEBUG_MULTIMODAL_HEURISTIC
+#include <iostream>
+#define DEBUG_MMH_MSG(str) do { std::cout << "[MMH] " << str << std::endl; } while( false )
+#else
+#define DEBUG_MMH_MSG(str) do { } while ( false )
+#endif
+
+class MultimodalModularHeuristic {
+private:
+    //Exterior members that the algorithm references or acts upon
+    const Graph* _graph{nullptr};
+    const std::vector<Request>* _requestsVect{nullptr};
+    SAEVRoute* _route{nullptr}; //_route is a pointer here to decorrelate the route from the algorithm and facilitate using multiple modules on the same route
+
+    //TODO: Move the diverse components (min/max constraints, get best entries/exits list etc) to their own classes to be used as delegates/decorator-like pattern
+    //      This would have the added benefit of easier customisation. Just have a common "apply()" function, and give useful data in constructor
+    //      => every inheritor just has an apply() method, used here, returning the appropriate data,
+    //         but child class constructor can be free to have all the data we want
+
+    /**
+     * Vector holding every entry sub request created to prevent refs staleness
+     * while letting the requests vector hold only the base requests \n
+     * \n
+     * Initialized with empty requests to match the number of base requests in the constructor
+     */
+    std::vector<Request> _entrySubRequests;
+    /**
+     * Vector holding every exit sub request created to prevent refs staleness
+     * while letting the requests vector hold only the base requests\n
+     * \n
+     * Initialized with empty requests to match the number of base requests in the constructor
+     */
+    std::vector<Request> _exitSubRequests;
+
+    /**
+     * Marks unfulfilled entry requests due to not finding a single viable candidate
+     */
+    std::vector<bool> _unfulfilledTransitEntry;
+    /**
+     * Marks unfulfilled exit requests due to not finding a single viable candidate
+     */
+    std::vector<bool> _unfulfilledTransitExit;
+
+    /**
+     * Vector containing every base request (referenced via its _requestsVect index) that needs to be fulfilled in SAEV-only DARP \n
+     * \n
+     * This includes non-multimodal requests and requests that couldn't be fulfilled in the entry/exit step of the multimodal procedure
+     */
+    std::vector<size_t> _darpRequestList;
+
+    //Add friend test classes to test inner workings without making the whole API public
+    FRIEND_TEST(MultimodalInsertionHeuristicDebug, DebugPTInstance);
+    FRIEND_TEST(MultimodalInsertionHeuristicDebug, DebugInstanceAlain);
+
+//Public interface to interact with the modular heuristic
+public:
+    MultimodalModularHeuristic(const Graph *graph, SAEVRoute *route, std::vector<Request> const* requestsVect) : _graph(graph), _requestsVect(requestsVect),
+    _route(route), _entrySubRequests(requestsVect->size()), _exitSubRequests(requestsVect->size()),
+    _unfulfilledTransitEntry(requestsVect->size()), _unfulfilledTransitExit(requestsVect->size()) {}
+
+    /**
+     * Executes iteratively each steps of the algorithm :
+     * <ol>
+     *  <li> Iterate over all base requests to generate and insert transit entry sub-request </li>
+     *  <li> From those inserted entry sub-requests, find compatible exit candidates and try to insert them </li>
+     *  <li> Insert every non-multimodal base request or previously unsuccessful multimodal base request for which step 1 or 2 failed </li>
+     * </ol>
+     *
+     * If we fail step 1 or 2 for a multimodal request or the request is non-multimodal,
+     * it's added to a list of DARP requests to be fulfilled \n
+     * If we fail step 2 (no compatible exit available), remove the entry request and reset+propagate bounds
+     */
+    void doMultimodalRequestsInsertion();
+
+    [[nodiscard]] size_t getNbBaseRequests() const {
+        return _requestsVect->size();
+    }
+
+    [[nodiscard]] const std::vector<Request> &getEntrySubRequests() const;
+    [[nodiscard]] const std::vector<Request> &getExitSubRequests() const;
+
+    [[nodiscard]] const std::vector<bool> &getUnfulfilledTransitEntry() const;
+    [[nodiscard]] const std::vector<bool> &getUnfulfilledTransitExit() const;
+
+    //Define useful struct to order transit access objects
+    struct ScoredTransitAccess : public TransitAccess {
+        double score{DBL_MAX};
+        explicit ScoredTransitAccess(const TransitAccess& access, double scr) : TransitAccess(access), score(scr) {}
+    };
+    using transit_order_function = std::function<bool(ScoredTransitAccess, ScoredTransitAccess)>;
+
+//Private members for heuristic internal functions we don't wish to see overriden
+private:
+
+    const Request & insertBestTransitEntryInRoute(const Request &baseRequest, size_t baseRequestId);
+    const Request & insertBestTransitExitsInRoute(const Request &baseRequest, size_t baseRequestId);
+
+
+    const Request &insertBestTransitAccessInRoute(const Request &baseRequest, const std::vector<TransitAccess> &transitAccessList,
+                                   size_t baseRequestId, bool isEntry);
+    const Request &insertBestTransitAccessInRoute(const std::vector<Request> &accessSubRequestsList,
+                                                  size_t baseRequestId, bool isEntry);
+
+    //Best candidates function
+    /**
+     * Creates and returns a vector of TransitAccess objects representing possible
+     * transit entries that can be converted to Request objects to try and insert them
+     * in a vehicle. <br>
+     * <br>
+     * This vector is sorted in the same way as Node._bestStationsNodeIdxVector and has max size
+     * Constants::MAX_TRANSIT_ENTRY_CANDIDATES. There can be less or even 0 elements if no candidate
+     * has a transit departure tmax in getMinEntryConstraint <= tmax <= getMaxEntryConstraint
+     *
+     * @param baseRequest the request we wish to find potential transit entries for
+     * @return A vector consisting of all valid TransitAccess objects wrt the base request
+     * and the min/max exit constraints. If no valid access is found, we return an empty vector.
+     */
+    [[nodiscard]] std::vector<TransitAccess> getBestTransitEntriesList(const Request &baseRequest) const;
+
+    /**
+     * Creates and returns a vector of TransitAccess objects representing possible
+     * transit entries that can be converted to Request objects to try and insert them
+     * in a vehicle. <br>
+     * <br>
+     * This vector is sorted according to the sort function the class was initialized with
+     * (default = t' + T(y,Dr)). It does not guarantee a max size of Constants::MAX_TRANSIT_EXIT_CANDIDATES.
+     * There can be less, or even 0 elements if no candidate has a transit departure tmax in
+     * getMinEntryConstraint <= tmax <= getMaxEntryConstraint
+     *
+     * @param baseRequestId id of the base request for which we look for best exits
+     * @return A vector consisting of all valid TransitAccess objects wrt the entry request's state
+     * and the min/max entry constraints. If no valid access is found, we return an empty vector.
+     */
+    std::vector<TransitAccess> getBestTransitExitsList(size_t baseRequestId);
+    /**
+    * Creates and returns a vector of TransitAccess objects representing possible
+    * transit entries that can be converted to Request objects to try and insert them
+    * in a vehicle. <br>
+    * <br>
+    * This vector is sorted according to the sort function the class was initialized with
+    * (default = t' + T(y,Dr)). It does not guarantee a max size of Constants::MAX_TRANSIT_EXIT_CANDIDATES.
+    * There can be less, or even 0 elements if no candidate has a transit departure tmax in
+    * getMinEntryConstraint <= tmax <= getMaxEntryConstraint
+    *
+    * @param baseRequestId id of the base request for which we look for best exits
+    * @return A vector consisting of all valid TransitAccess objects wrt the entry request's state
+    * and the min/max entry constraints. If no valid access is found, we return an empty vector.
+    */
+    [[nodiscard]] std::vector<TransitAccess>
+    getBestTransitExitsList(size_t baseRequestId, const Request &baseRequest,
+                            const SAEVKeyPoint &entrySubRequestOriginKP) const;
+
+//Protected member function for overriding as we make this more modular
+protected:
+    //Entry filter
+    [[nodiscard]] uint getMinEntryConstraint(const Request &request, size_t ptEntryNodeIdx) const;
+    [[nodiscard]] uint getMaxEntryConstraint(const Request &request, size_t ptEntryNodeIdx) const;
+    //Exit filter
+    /**
+     * Base implementation of min exit constraint.
+     * This implementation is equivalent to no min constraint, as our subset of possible moves already filters our options a bit
+     * @param baseRequestId Id of the base request for which we generate exit subrequests. Necessary to get data on base request and entry subrequest if necessary
+     * @param transitExitNodeIdx exit node index
+     * @return 0
+     */
+    [[nodiscard]] uint getMinExitConstraint(size_t baseRequestId, const SAEVKeyPoint &entrySubRequestOriginKP,
+                                            size_t transitExitNodeIdx) const;
+    /**
+     * Base implementation of max exit constraint.
+     * This base implementation just checks for arrival validity wrt the base request's max time window
+     * @param baseRequestId Id of the base request for which we generate exit subrequests. Necessary to get data on base request and entry subrequest if necessary
+     * @param transitExitNodeIdx exit node index
+     * @return baseRequest.DestinationTW.max - T(exitData.Node, baseRequest.Destination)
+     */
+    [[nodiscard]] uint getMaxExitConstraint(size_t baseRequestId, const SAEVKeyPoint &entrySubRequestOriginKP,
+                                            size_t transitExitNodeIdx) const;
+    /**
+     * Base implementation of a sorting score (lower is better) for exit candidates.
+     * This implementation scores via T(exitNode, destinationNode) + exitTime to try and
+     * incentivize early and/or close access points. <br> <br>
+     * Override this function to alter exit candidates ordering via the score function <br>
+     * /!\ Be mindful that vectors are sorted in ascending order, so invert the score or override getScoredTransitExitOrderer if you want greater values first /!\
+     * @param baseRequest the base request, required to get the destination
+     * @param exitData exit data containing the exit point and timestamp
+     * @return A score allowing to sort transit exits in ascending score order
+     */ //TODO: try other scoring functions (current other idea : T(exitNode, destinationNode) * exitTime; alpha*T(exitNode, destinationNode) + exitTime)
+    [[nodiscard]] double getTransitExitScore(const Request& baseRequest, const TransitAccess& exitData) const;
+    /**
+     * This function returns an orderer function used to sort the transit exit priority queue. <br> <br>
+     * Only override this function if you need a more involved kind of sort function that can't be made by simply overriding getTransitExitScore. <br>
+     * /!\ Be mindful that vectors are sorted in ascending order, so invert the order if you want greater values first /!\
+     * @return Returns a function taking in argument two ScoredTransitAccess objects and returning a true if lhs is to be ordered before rhs, false otherwise
+     */
+    [[nodiscard]] static transit_order_function getScoredTransitExitOrderer() ;
+
+    //Keep those getters/setters protected as no other member should access or modify these objects
+    [[nodiscard]] const Graph *getGraph() const;
+    void setGraph(const Graph *graph);
+
+    [[nodiscard]] SAEVRoute *getRoute() const;
+    void setRoute(SAEVRoute *route);
+
+    void updateSubRequest(size_t requestId, const Request &request, bool isEntry);
+
+    const Request &getSubrequest(size_t requestId, bool isEntry);
+
+    [[nodiscard]] double getTransitExitScore(size_t transitExitNodeIndex, size_t requestDestinationNodeIndex,
+                               uint transitExitTimestamp) const;
+
+    void updateUnfulfilledSubrequest(size_t baseRequestId, bool isEntry, bool value);
+
+    bool isEntryFulfilled(size_t baseRequestId);
+
+    void emplaceDARPRequest(size_t baseRequestId);
+};
+
+
+#endif //GREEDYALGORITHM_MULTIMODALMODULARHEURISTIC_H
diff --git a/src/algorithm/Multimodal/Heuristics/TransitAccess.h b/src/algorithm/Multimodal/Heuristics/TransitAccess.h
new file mode 100644
index 0000000000000000000000000000000000000000..c59a121b2a11ea5d7b80d4a1e9a191c8391d05b8
--- /dev/null
+++ b/src/algorithm/Multimodal/Heuristics/TransitAccess.h
@@ -0,0 +1,39 @@
+//
+// Created by Romain on 24/07/2024.
+//
+
+#ifndef GREEDYALGORITHM_TRANSITACCESS_H
+#define GREEDYALGORITHM_TRANSITACCESS_H
+
+
+#include <cstddef>
+#include "../../../utils/Globals.h"
+
+class TransitAccess {
+    size_t _accessNodeIdx{};
+    uint _accessTimestamp{};
+
+public:
+    TransitAccess() = default;
+    TransitAccess(size_t accessNodeIdx, uint accessTimestamp) : _accessNodeIdx(accessNodeIdx),
+                                                                _accessTimestamp(accessTimestamp) {}
+
+    [[nodiscard]] size_t getAccessNodeIdx() const {
+        return _accessNodeIdx;
+    }
+
+    void setAccessNodeIdx(size_t accessNodeIdx) {
+        _accessNodeIdx = accessNodeIdx;
+    }
+
+    [[nodiscard]] uint getAccessTimestamp() const {
+        return _accessTimestamp;
+    }
+
+    void setAccessTimestamp(uint accessTimestamp) {
+        _accessTimestamp = accessTimestamp;
+    }
+};
+
+
+#endif //GREEDYALGORITHM_TRANSITACCESS_H
diff --git a/src/ShortestPath/ShortestPath.h b/src/algorithm/ShortestPath/ShortestPath.h
similarity index 78%
rename from src/ShortestPath/ShortestPath.h
rename to src/algorithm/ShortestPath/ShortestPath.h
index 8ad32901153e96d46f5414919f1c3313bdcf3462..713c797be7a4005494a90a086983c42db7fc49b7 100644
--- a/src/ShortestPath/ShortestPath.h
+++ b/src/algorithm/ShortestPath/ShortestPath.h
@@ -20,8 +20,16 @@ public:
         return _keyPoints;
     }
 
+    virtual KeyPoint getDeparture() const {
+        return *_keyPoints.cbegin();
+    }
+
+    virtual KeyPoint getArrival() const {
+        return *(_keyPoints.cend() - 1);
+    }
+
     void replaceKeyPoint(int keyPointIndex, KeyPoint& value) {
-        _keyPoints.at(keyPointIndex) = value;
+        _keyPoints[keyPointIndex] = value;
     }
 
     /**
diff --git a/src/ShortestPath/Transit/TransitAlgorithmState.cpp b/src/algorithm/ShortestPath/Transit/TransitAlgorithmState.cpp
similarity index 100%
rename from src/ShortestPath/Transit/TransitAlgorithmState.cpp
rename to src/algorithm/ShortestPath/Transit/TransitAlgorithmState.cpp
diff --git a/src/ShortestPath/Transit/TransitAlgorithmState.h b/src/algorithm/ShortestPath/Transit/TransitAlgorithmState.h
similarity index 65%
rename from src/ShortestPath/Transit/TransitAlgorithmState.h
rename to src/algorithm/ShortestPath/Transit/TransitAlgorithmState.h
index d069858f14e66d27b6590f9c72a434d338c81393..6409af40e1af2378ff45db3572306bd1e2516be8 100644
--- a/src/ShortestPath/Transit/TransitAlgorithmState.h
+++ b/src/algorithm/ShortestPath/Transit/TransitAlgorithmState.h
@@ -8,71 +8,54 @@
 
 #include <array>
 #include <cstdint>
-#include "../../instance/graph/LineStop.h"
+#include "../../../instance/graph/LineStop.h"
+#include "../../../utils/Constants.h"
 
 class TransitAlgorithmState {
 private:
     int _nodeIndex;
-    int _instant;
-    int _passageIndex;
-    std::vector<LineStop> _connections;
-    int _precedingNodeIndex;
+    int _instant{INT16_MAX};
+    int _passageIndex{-1};
+    std::vector<std::reference_wrapper<const LineStop>> _connections;//{Constants::MAX_TRANSIT_CONNECTIONS};
+    int _precedingNodeIndex{-1};
 
 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(int currentNode, int currentInstant, int currentPassageIndex, int precedingNodeIndex) :
+            _nodeIndex(currentNode), _instant(currentInstant),
+            _passageIndex(currentPassageIndex), _precedingNodeIndex(precedingNodeIndex) {
+        _connections.reserve(Constants::MAX_TRANSIT_CONNECTIONS);
     }
 
-    TransitAlgorithmState(TransitAlgorithmState& baseState) {
-        _nodeIndex = baseState.getNodeIndex();
-        _instant = baseState.getInstant();
-        _passageIndex = baseState.getPassageIndex();
-        _precedingNodeIndex = baseState.getPrecedingNodeIndex();
-
+    TransitAlgorithmState(TransitAlgorithmState const& 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.reserve(Constants::MAX_TRANSIT_CONNECTIONS);
+        for(const 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();
-
+    TransitAlgorithmState(TransitAlgorithmState const& 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.reserve(Constants::MAX_TRANSIT_CONNECTIONS);
+        for(const 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(int nodeIndex) : _nodeIndex(nodeIndex) {
+        _connections.reserve(Constants::MAX_TRANSIT_CONNECTIONS);
     }
 
-    explicit TransitAlgorithmState() {
-        _nodeIndex = -1;
-        _instant = INT16_MAX;
-        _passageIndex = -1;
-        _precedingNodeIndex = -1;
-        _connections.reserve(2);
+    explicit TransitAlgorithmState() : _nodeIndex(-1) {
+        _connections.reserve(Constants::MAX_TRANSIT_CONNECTIONS);
     }
 
     [[nodiscard]] int getNodeIndex() const {
@@ -87,7 +70,7 @@ public:
         return _passageIndex;
     }
 
-    [[nodiscard]] const std::vector<LineStop> &getConnections() const {
+    [[nodiscard]] const std::vector<std::reference_wrapper<const LineStop>> &getConnections() const {
         return _connections;
     }
 
@@ -96,19 +79,19 @@ public:
     }
 
     [[nodiscard]] bool canAddConnection() const {
-        return _connections.size() < 2;
+        return _connections.size() < Constants::MAX_TRANSIT_CONNECTIONS;
     }
 
     [[nodiscard]] size_t getNbConnections() const {
         return _connections.size();
     }
 
-    [[nodiscard]] LineStop getLastConnectionLineStop() const {
+    [[nodiscard]] const LineStop& getLastConnectionLineStop() const {
         return _connections.back();
     }
 
-    [[nodiscard]] Line getLastConnectionLine() const {
-        return _connections.back().getLineRef();
+    [[nodiscard]] const Line& getLastConnectionLine() const {
+        return _connections.back().get().getLineRef();
     }
 
     [[nodiscard]] int getPrecedingNodeIndex() const {
@@ -120,7 +103,7 @@ public:
     }
 
     [[nodiscard]] int getNextNodeIndex() const {
-        return _connections.back().getNextNodeIndex();
+        return _connections.back().get().getNextNodeIndex();
     }
 
     void setNodeIndex(int nodeIndex) {
@@ -150,8 +133,6 @@ public:
         }
     }
 
-    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
@@ -161,7 +142,7 @@ public:
     [[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) /***/
+//        TODO : check  && (this->getLastConnectionLine() == rhs.getLastConnectionLine() || this->getNbConnections() == Constants::MAX_TRANSIT_CONNECTIONS)
         && ((this->getInstant() < rhs.getInstant() && this->getConnections().size() <= rhs.getConnections().size())
         || (this->getInstant() == rhs.getInstant() && this->getConnections().size() < rhs.getConnections().size()));
     }
@@ -172,6 +153,8 @@ public:
      * @return
      */
     bool operator<(const TransitAlgorithmState& rhs) const {
+        if(this->_nodeIndex != -1 && rhs._nodeIndex == -1)
+            return true;
         return this->_nodeIndex == rhs.getNodeIndex() //same current node
                 && (this->getInstant() < rhs.getInstant() //strictly better time
                 || (this->getInstant() == rhs.getInstant()
@@ -179,6 +162,8 @@ public:
     }
 
     bool operator>(const TransitAlgorithmState& rhs) const {
+        if(this->_nodeIndex != -1 && rhs._nodeIndex == -1)
+            return false;
         return this->_nodeIndex == rhs.getNodeIndex() //same current node
                 && (this->getInstant() > rhs.getInstant()
                 || (this->getInstant() == rhs.getInstant()
@@ -197,8 +182,6 @@ public:
                 || 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);
 
@@ -207,11 +190,11 @@ public:
             res += ", Connections: ";
             if(_connections.size() > 1) {
                 for(int i = 0; i < _connections.size() - 1; ++i) {
-                    res += _connections.at(i).getLineRef().getLineId() + " -> ";
+                    res += _connections[i].get().getLineRef().getLineId() + " -> ";
                 }
             }
 
-            res += _connections.at(_connections.size() - 1).getLineRef().getLineId();
+            res += _connections[_connections.size() - 1].get().getLineRef().getLineId();
 
         }
 
diff --git a/src/ShortestPath/Transit/TransitShortestPath.h b/src/algorithm/ShortestPath/Transit/TransitShortestPath.h
similarity index 53%
rename from src/ShortestPath/Transit/TransitShortestPath.h
rename to src/algorithm/ShortestPath/Transit/TransitShortestPath.h
index c2014d13c908b8defe30222a45c3fcd9d905519d..fc3ebcd2ec1f1a42b2eefae91df7ea64b28c05f4 100644
--- a/src/ShortestPath/Transit/TransitShortestPath.h
+++ b/src/algorithm/ShortestPath/Transit/TransitShortestPath.h
@@ -6,17 +6,22 @@
 #define GREEDYALGORITHM_TRANSITSHORTESTPATH_H
 
 #include "../ShortestPath.h"
-#include "../../instance/graph/LineStop.h"
+#include "../../../instance/graph/LineStop.h"
 #include "TransitAlgorithmState.h"
 
-class TransitShortestPath : public ShortestPath<LineStop> {
+class TransitShortestPath : public ShortestPath<std::reference_wrapper<const LineStop>> {
 private:
-    int _arrivalTime;
+    int _arrivalTime{-1};
+    int _arrivalNode{-1};
 public:
+    TransitShortestPath() = default;
 
     explicit TransitShortestPath(const TransitAlgorithmState& state) {
-        _arrivalTime = state.getInstant();
-        std::move(state.getConnections().begin(), state.getConnections().end() - 1,_keyPoints.begin());
+        if(state.getNbConnections() > 0) {
+            _keyPoints = state.getConnections();
+            _arrivalNode = state.getNodeIndex();
+            _arrivalTime = state.getInstant();
+        }
     }
 
     /**
@@ -39,24 +44,23 @@ public:
                (this->getArrivalTime() == rhs.getArrivalTime() && this->getKeyPoints().size() > rhs.getKeyPoints().size());
     }
 
-    [[nodiscard]] int getArrivalTime() const { return _arrivalTime; }
+    [[nodiscard]] std::reference_wrapper<const LineStop> getDeparture() const override {
+        return *_keyPoints.cbegin();
+    }
 
+    [[nodiscard]] std::reference_wrapper<const LineStop> getArrival() const override {
+        return *(_keyPoints.cend() - 1);
+    }
 
-//    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());
-//    }
+    [[nodiscard]] size_t getDepartureNode() const {
+        return getDeparture().get().getNodeIndex();
+    }
+
+    [[nodiscard]] size_t getArrivalNode() const {
+        return _arrivalNode;
+    }
+
+    [[nodiscard]] int getArrivalTime() const { return _arrivalTime; }
 };
 
 
diff --git a/src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.cpp b/src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..058bad5b8f15bf072980ed0ccc7da761f6068816
--- /dev/null
+++ b/src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.cpp
@@ -0,0 +1,50 @@
+//
+// Created by romain on 13/03/24.
+//
+
+#include <algorithm>
+#include "TransitShortestPathContainer.h"
+#include "TransitStateContainer.h"
+
+void TransitShortestPathContainer::addShortestPathCollection(size_t startNodeIndex,
+                                                             const std::pair<size_t , std::vector<TransitShortestPath>>& shortestPathList) {
+    container[startNodeIndex].emplace_back(shortestPathList);
+}
+
+void TransitShortestPathContainer::addShortestPathCollection(size_t startNodeIndex, uint startingInstant, size_t graphSize,
+                                                             const TransitStateContainer& algorithmResultStates) {
+    std::vector<TransitShortestPath> shortestPathList;
+    shortestPathList.reserve(graphSize);
+
+    //Convert states to shortest paths and add to collection
+    for(size_t i = 0; i < graphSize; ++i) {
+        shortestPathList.emplace_back(algorithmResultStates.getBestSolution(i));
+    }
+
+    //Add the (startingInstant, pathVector) pair at the appropriate node index
+    container[startNodeIndex].emplace_back(startingInstant, shortestPathList);
+}
+
+const std::pair<size_t, std::vector<TransitShortestPath>> &
+TransitShortestPathContainer::getShortestPathsFrom(size_t startNodeIndex, uint earliestStartInstant) const {
+    const auto& iterator = std::ranges::lower_bound(container[startNodeIndex],
+                                            std::pair<size_t , std::vector<TransitShortestPath>>(earliestStartInstant, {}));
+
+    return *iterator;
+}
+
+TransitShortestPath
+TransitShortestPathContainer::getShortestPathToYFromTime(size_t startNodeIndex, uint earliestStartInstant, size_t goalNode) const {
+    auto [startNode, shortestPathsVector] = getShortestPathsFrom(startNodeIndex, earliestStartInstant);
+    //Check if destination vector size is the same as the container's which means we have a cell for every graph node => O(1) access
+    if(shortestPathsVector.size() == container.size())  {
+        return shortestPathsVector[goalNode];
+    } else {
+        for (const auto &shortestPath: shortestPathsVector) {
+            if (shortestPath.getArrivalNode() == goalNode) {
+                return shortestPath;
+            }
+        }
+    }
+    return  TransitShortestPath{};
+}
diff --git a/src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h b/src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..22730ceeef7e45a4794146108e8b16d1ef2ae98c
--- /dev/null
+++ b/src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h
@@ -0,0 +1,27 @@
+//
+// 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<size_t, 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(size_t startNodeIndex, const std::pair<size_t , std::vector<TransitShortestPath>>& shortestPathList);
+    void addShortestPathCollection(size_t startNodeIndex, uint startingInstant, size_t graphSize, const TransitStateContainer& algorithmResultStates);
+    [[nodiscard]] const std::pair<size_t, std::vector<TransitShortestPath>> & getShortestPathsFrom(size_t startNodeIndex, uint earliestStartInstant) const;
+    [[nodiscard]] TransitShortestPath getShortestPathToYFromTime(size_t startNodeIndex, uint earliestStartInstant, size_t goalNode) const;
+};
+
+
+#endif //GREEDYALGORITHM_TRANSITSHORTESTPATHCONTAINER_H
diff --git a/src/ShortestPath/Transit/TransitShortestPathPrecompute.cpp b/src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
similarity index 78%
rename from src/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
rename to src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
index 20dbf836d3e8296f9cbe7491e534cee4ae3c4d7c..2c44f0fc55bbf0c83a71d53df8904f9a238a14fc 100644
--- a/src/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
+++ b/src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
@@ -8,9 +8,9 @@
 
 #ifdef DEBUG_TRANSIT_PRECOMPUTE
 #include <iostream>
-#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
+#define DEBUG_TRANSIT_MSG(str) do { std::cout << "[TRANSIT] " << str << std::endl; } while( false )
 #else
-#define DEBUG_MSG(str) do { } while ( false )
+#define DEBUG_TRANSIT_MSG(str) do { } while ( false )
 #endif
 
 //TODO:
@@ -23,26 +23,26 @@ TransitStateContainer TransitShortestPathPrecompute::executeAlgorithm(const Grap
     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();
+        TransitAlgorithmState 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()))
+            DEBUG_TRANSIT_MSG("\n\nComparing state " + currentState.toString() + " and " + solutionsContainer.getBestSolution(currentState.getNodeIndex(), currentState.getNbConnections()).toString());
+            for (const 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));
+                    DEBUG_TRANSIT_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());
+                            nextPassageIndex = lineStop.findNextScheduledPassageIdx(lineStop.getStopIndex(),
+                                                                                    currentState.getInstant());
                             if (nextPassageIndex == lineStop.getLineRef().scheduleSize()) {
                                 newState.setNodeIndex(-1);
                             } else {
@@ -66,17 +66,17 @@ TransitStateContainer TransitShortestPathPrecompute::executeAlgorithm(const Grap
                         }
                     }
 
-                    DEBUG_MSG("Created new state " + newState.toString());
+                    DEBUG_TRANSIT_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");
+                        DEBUG_TRANSIT_MSG("Candidate state " + newState.toString() + " added to priority queue\n");
                         solutionsContainer.replaceBestSolutionIfNecessary(nextNode, newState);
                         statePriorityQueue.emplace(newState);
                     }
                 }
             }
-            DEBUG_MSG("Done extending state " + currentState.toString());
+            DEBUG_TRANSIT_MSG("Done extending state " + currentState.toString());
         }
     }
 
diff --git a/src/ShortestPath/Transit/TransitShortestPathPrecompute.h b/src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h
similarity index 65%
rename from src/ShortestPath/Transit/TransitShortestPathPrecompute.h
rename to src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h
index 229cd0c491bce4391041046c8e798f9d19dbbfa3..22d8ddcc509a39d3d0476e06d5413c03ec74c4cf 100644
--- a/src/ShortestPath/Transit/TransitShortestPathPrecompute.h
+++ b/src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h
@@ -5,14 +5,12 @@
 #ifndef GREEDYALGORITHM_TRANSITSHORTESTPATHPRECOMPUTE_H
 #define GREEDYALGORITHM_TRANSITSHORTESTPATHPRECOMPUTE_H
 
-#include "../TimeDependentShortestPathContainer.h"
 #include "TransitShortestPath.h"
-#include "../../instance/graph/Graph.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);
 };
 
diff --git a/src/ShortestPath/Transit/TransitStateContainer.h b/src/algorithm/ShortestPath/Transit/TransitStateContainer.h
similarity index 63%
rename from src/ShortestPath/Transit/TransitStateContainer.h
rename to src/algorithm/ShortestPath/Transit/TransitStateContainer.h
index fbb3ea4a263190fcc4a30b8e92fc11da70a7bab5..46023fac62b862549b0ad4834363734cfb5f15e6 100644
--- a/src/ShortestPath/Transit/TransitStateContainer.h
+++ b/src/algorithm/ShortestPath/Transit/TransitStateContainer.h
@@ -10,9 +10,9 @@
 
 #ifdef DEBUG_TRANSIT_PRECOMPUTE
 #include <iostream>
-#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
+#define DEBUG_TRANSIT_MSG(str) do { std::cout << "[TRANSIT] " << str << std::endl; } while( false )
 #else
-#define DEBUG_MSG(str) do { } while ( false )
+#define DEBUG_TRANSIT_MSG(str) do { } while ( false )
 #endif
 
 class TransitStateContainer {
@@ -21,21 +21,13 @@ private:
     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();
+            solutionVector[i].emplace_back();
+            solutionVector[i].emplace_back();
         }
     }
     /**
@@ -44,14 +36,14 @@ public:
      * @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);
+        if(solutionVector[nodeIndex][1] < solutionVector[nodeIndex][0])
+            return solutionVector[nodeIndex][1];
         else
-            return solutionVector.at(nodeIndex).at(0);
+            return solutionVector[nodeIndex][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);}
+    TransitAlgorithmState& getBestSolution(int nodeIndex, int nbConnections){ return solutionVector[nodeIndex][nbConnections == 0 ? 0 : nbConnections - 1];}
+    std::vector<TransitAlgorithmState>& getSolutions(int nodeIndex){ return solutionVector[nodeIndex];}
     std::vector<std::vector<TransitAlgorithmState>>& getSolutionsVector(){ return solutionVector;}
 
     bool strictlyDominates(const TransitAlgorithmState& state){
@@ -72,10 +64,10 @@ public:
     void replaceBestSolutionIfNecessary(int nodeIndex, const TransitAlgorithmState& newState)
     {
         if(newState.getNbConnections() > 0) {
-            TransitAlgorithmState& currentBest = solutionVector.at(nodeIndex).at(newState.getNbConnections() - 1);
+            TransitAlgorithmState const& currentBest = solutionVector[nodeIndex][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;
+                DEBUG_TRANSIT_MSG("Candidate state " + newState.toString() + " is the new fastest solution");
+                solutionVector[nodeIndex][newState.getNbConnections() - 1] = newState;
             }
         }
     }
@@ -87,10 +79,10 @@ public:
      * @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);
+        TransitAlgorithmState currentBestSol = solutionVector[nodeIndex][0];
+        for(size_t i = 1; i < solutionVector[nodeIndex].size(); ++i) {
+            if(solutionVector[nodeIndex][i].getInstant() < currentBestSol.getInstant()) {
+                currentBestSol = solutionVector[nodeIndex][i];
             }
         }
 
@@ -100,7 +92,7 @@ public:
     void pushEmptyState(int nodeIndex)
     {
         TransitAlgorithmState newState = TransitAlgorithmState(nodeIndex);
-        solutionVector.at(nodeIndex).emplace_back(newState);
+        solutionVector[nodeIndex].emplace_back(newState);
     }
 };
 
diff --git a/src/algorithm/ShortestPath/Vehicle/ClosestDestinationsContainer.h b/src/algorithm/ShortestPath/Vehicle/ClosestDestinationsContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..d78f7886ccb21c79c94b1dbbc8773150fd6755a7
--- /dev/null
+++ b/src/algorithm/ShortestPath/Vehicle/ClosestDestinationsContainer.h
@@ -0,0 +1,46 @@
+//
+// Created by romain on 22/07/24.
+//
+
+#ifndef GREEDYALGORITHM_CLOSESTDESTINATIONSCONTAINER_H
+#define GREEDYALGORITHM_CLOSESTDESTINATIONSCONTAINER_H
+
+
+#include <cstddef>
+#include <utility>
+#include <queue>
+#include <set>
+#include "VehicleShortestPath.h"
+#include "VehicleDestination.h"
+
+class ClosestDestinationsContainer {
+private:
+    size_t _startingNodeIdx;
+    std::set<VehicleDestination> _orderedDestinations{};
+
+public:
+    explicit ClosestDestinationsContainer(size_t startingNodeIdx) : _startingNodeIdx(startingNodeIdx) {}
+
+    size_t getStartingNodeIdx() const {
+        return _startingNodeIdx;
+    }
+
+    void setStartingNodeIdx(size_t startingNodeIdx) {
+        _startingNodeIdx = startingNodeIdx;
+    }
+
+    [[nodiscard]] const std::set<VehicleDestination> &getOrderedDestinations() const {
+        return _orderedDestinations;
+    }
+
+    auto cbegin() {
+        return _orderedDestinations.cbegin();
+    }
+
+    void addDestination(size_t destinationNodeIdx, uint distance) {
+        _orderedDestinations.emplace(destinationNodeIdx, distance);
+    }
+};
+
+
+#endif //GREEDYALGORITHM_CLOSESTDESTINATIONSCONTAINER_H
diff --git a/src/algorithm/ShortestPath/Vehicle/MatrixShortestPathContainer.h b/src/algorithm/ShortestPath/Vehicle/MatrixShortestPathContainer.h
new file mode 100644
index 0000000000000000000000000000000000000000..f31224d200f0c49cac014dfe35b0e75864866311
--- /dev/null
+++ b/src/algorithm/ShortestPath/Vehicle/MatrixShortestPathContainer.h
@@ -0,0 +1,64 @@
+//
+// Created by romain on 16/07/24.
+//
+
+#ifndef GREEDYALGORITHM_MATRIXSHORTESTPATHCONTAINER_H
+#define GREEDYALGORITHM_MATRIXSHORTESTPATHCONTAINER_H
+
+
+#include <vector>
+#include <cstdint>
+#include <algorithm>
+#include "../../../utils/Globals.h"
+
+class MatrixShortestPathContainer {
+private:
+    std::vector<std::vector<uint>> _distanceMatrix{};
+public:
+    MatrixShortestPathContainer() = default;
+
+    /**
+     * Moves the given shortestPathsMatrix to initialize the container with the given distances.
+     * @param shortestPathsMatrix
+     */
+    explicit MatrixShortestPathContainer(std::vector<std::vector<uint>>& shortestPathsMatrix) : _distanceMatrix(std::move(shortestPathsMatrix)) {}
+
+    explicit MatrixShortestPathContainer(size_t matrixSize) {
+        _distanceMatrix.resize(matrixSize);
+        for(size_t i = 0; i < _distanceMatrix.capacity(); ++i)
+            _distanceMatrix[i].resize(matrixSize, INT16_MAX);
+    }
+
+    [[nodiscard]] const std::vector<std::vector<uint>> &getDistanceMatrix() const {
+        return _distanceMatrix;
+    }
+
+    /**
+     * Replaces all the results for the given starting node with the given results vector.
+     * the whole vector is cleared before moving the results vector data
+     * @param startingNodeIdx starting node of the paths in our results/destination distance vector
+     * @param results a vector which will be moved to this current results collection
+     */
+    void replaceResultsCollection(size_t startingNodeIdx, std::vector<uint>& results) {
+        _distanceMatrix[startingNodeIdx].clear();
+        std::ranges::move(results, _distanceMatrix[startingNodeIdx].begin());
+    }
+
+    /**
+     * Gives the shortest path time from a given node index to a given destination node index
+     * @param from origin node index
+     * @param to destination node index
+     * @return the distance of the shortest path between those two nodes
+     */
+    uint getShortestPath(size_t from, size_t to) { return _distanceMatrix[from][to]; }
+
+    /**
+     *
+     * @param idx Node index from
+     * @return The collection of distances to get to every graph node from the given index
+     */
+    std::vector<uint>& operator[](size_t idx) { return _distanceMatrix[idx]; }
+};
+
+
+#endif //GREEDYALGORITHM_MATRIXSHORTESTPATHCONTAINER_H
diff --git a/src/algorithm/ShortestPath/Vehicle/VehicleDestination.h b/src/algorithm/ShortestPath/Vehicle/VehicleDestination.h
new file mode 100644
index 0000000000000000000000000000000000000000..423c40dec2883977741ccd0c21bc1e348793a1c3
--- /dev/null
+++ b/src/algorithm/ShortestPath/Vehicle/VehicleDestination.h
@@ -0,0 +1,46 @@
+//
+// Created by romain on 22/07/24.
+//
+
+#ifndef GREEDYALGORITHM_VEHICLEDESTINATION_H
+#define GREEDYALGORITHM_VEHICLEDESTINATION_H
+
+
+#include <cstddef>
+#include <cstdlib>
+#include <cstdint>
+#include <compare>
+
+class VehicleDestination {
+private:
+    size_t _destinationNodeIdx;
+    uint _distance{INT32_MAX};
+
+public:
+    explicit VehicleDestination(size_t destinationNodeIdx) : _destinationNodeIdx(destinationNodeIdx) {}
+    VehicleDestination(size_t destinationNodeIdx, uint distance) : _destinationNodeIdx(destinationNodeIdx),
+                                                                   _distance(distance) {}
+
+    [[nodiscard]] size_t getDestinationNodeIdx() const {
+        return _destinationNodeIdx;
+    }
+
+    void setDestinationNodeIdx(size_t destinationNodeIdx) {
+        _destinationNodeIdx = destinationNodeIdx;
+    }
+
+    [[nodiscard]] uint getDistance() const {
+        return _distance;
+    }
+
+    void setDistance(uint distance) {
+        _distance = distance;
+    }
+
+    auto operator<=>(const VehicleDestination &rhs) const {
+        return std::strong_order(_distance, rhs.getDistance());
+    }
+};
+
+
+#endif //GREEDYALGORITHM_VEHICLEDESTINATION_H
diff --git a/src/algorithm/ShortestPath/Vehicle/VehiclePathState.h b/src/algorithm/ShortestPath/Vehicle/VehiclePathState.h
new file mode 100644
index 0000000000000000000000000000000000000000..2907a68381a3e1686ab20b549dd2137d10fc6c07
--- /dev/null
+++ b/src/algorithm/ShortestPath/Vehicle/VehiclePathState.h
@@ -0,0 +1,43 @@
+//
+// Created by romain on 16/07/24.
+//
+
+#ifndef GREEDYALGORITHM_VEHICLEPATHSTATE_H
+#define GREEDYALGORITHM_VEHICLEPATHSTATE_H
+
+#include <limits>
+#include <cstdlib>
+#include <compare>
+
+class VehiclePathState {
+private:
+    size_t _nodeIndex{std::numeric_limits<size_t>::max()};
+    uint _instant{std::numeric_limits<uint>::max()};
+
+public:
+    VehiclePathState() = default;
+    VehiclePathState(size_t nodeIndex, uint instant) : _nodeIndex(nodeIndex), _instant(instant) {}
+
+    [[nodiscard]] size_t getNodeIndex() const {
+        return _nodeIndex;
+    }
+
+    void setNodeIndex(size_t nodeIndex) {
+        _nodeIndex = nodeIndex;
+    }
+
+    [[nodiscard]] uint getInstant() const {
+        return _instant;
+    }
+
+    void setInstant(uint instant) {
+        _instant = instant;
+    }
+
+    auto operator<=>(const VehiclePathState &rhs) const {
+        return _instant <=> rhs.getInstant();
+    }
+};
+
+
+#endif //GREEDYALGORITHM_VEHICLEPATHSTATE_H
diff --git a/src/algorithm/ShortestPath/Vehicle/VehicleShortestPath.h b/src/algorithm/ShortestPath/Vehicle/VehicleShortestPath.h
new file mode 100644
index 0000000000000000000000000000000000000000..918664e65b1e43c5a8133b411981d4e0dc1569b3
--- /dev/null
+++ b/src/algorithm/ShortestPath/Vehicle/VehicleShortestPath.h
@@ -0,0 +1,46 @@
+//
+// Created by romain on 16/07/24.
+//
+
+#ifndef GREEDYALGORITHM_VEHICLESHORTESTPATH_H
+#define GREEDYALGORITHM_VEHICLESHORTESTPATH_H
+
+#include <compare>
+#include "../ShortestPath.h"
+#include "../../../instance/graph/Node.h"
+#include "../../../utils/Globals.h"
+
+class VehicleShortestPath : public ShortestPath<size_t> {
+private:
+    uint _duration{0};
+public:
+    VehicleShortestPath() = default;
+    explicit VehicleShortestPath(uint duration) : _duration(duration) {}
+
+    [[nodiscard]] uint getDuration() const {
+        return _duration;
+    }
+
+    void setDuration(uint duration) {
+        _duration = duration;
+    }
+
+    [[nodiscard]] size_t getDeparture() const override {
+        return *_keyPoints.cbegin();
+    }
+
+    [[nodiscard]] size_t getArrival() const override {
+        return *(_keyPoints.cend() - 1);
+    }
+
+    auto operator<=>(const VehicleShortestPath &rhs) const {
+        if (this->getDeparture() != rhs.getDeparture() || this->getArrival() != rhs.getArrival()) {
+            return std::partial_ordering::unordered;
+        }
+
+        return std::partial_order(_duration, rhs.getDuration());
+    }
+};
+
+
+#endif //GREEDYALGORITHM_VEHICLESHORTESTPATH_H
diff --git a/src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp b/src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..159d27c1965e126ff5c73d1fc404aea98cfffdb4
--- /dev/null
+++ b/src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
@@ -0,0 +1,117 @@
+//
+// Created by romain on 16/07/24.
+//
+
+#include <queue>
+#include "VehicleShortestPathCalculation.h"
+#include "VehiclePathState.h"
+#include "../../../utils/Constants.h"
+
+std::vector<uint>
+VehicleShortestPathCalculation::computeShortestPathsFromNode(Graph &graph, size_t startingNodeIdx, bool useEdges) {
+    std::vector<uint> results;
+    results.resize(graph.getNbNodes(), INT16_MAX);
+
+    if(!useEdges) {
+        results = graph.getShortestSaevPaths()[startingNodeIdx];
+    } else {
+        results[startingNodeIdx] = 0; //Init value to 0 for path from X to X
+        for(const auto& edgeIndex : graph.getNode(startingNodeIdx).getOutgoingEdges()) { //Init  values according to immediate outgoing edges
+            const Edge & edge = graph.getEdge(edgeIndex);
+            results[edge.getEndNodeIdx()] = edge.getLength();
+        }
+    }
+
+    std::vector<bool> mark(graph.getNbNodes(),false);
+    std::priority_queue<VehiclePathState,std::vector<VehiclePathState>, std::greater<>> stateQueue{};
+
+    //Init state queue with the current distances to check
+    for(size_t i = 0; i < graph.getNbNodes(); ++i) {
+        stateQueue.emplace(i, results[i]);
+    }
+
+    while(!stateQueue.empty()) {
+        VehiclePathState currentState = stateQueue.top();
+        stateQueue.pop();
+        //Only expand and add
+        if(!mark[currentState.getNodeIndex()]) {
+            mark[currentState.getNodeIndex()] = true;
+
+            //Considering order of iteration is by shortest path to starting node, it's fair to add the current state node as a closest station
+            if(graph.getNbPTLines(currentState.getNodeIndex()) > 0 && graph.getNbClosestStations(startingNodeIdx) < Constants::MAX_CLOSEST_STATIONS_CANDIDATES) {
+                graph.emplaceBackClosestStation(startingNodeIdx, currentState.getNodeIndex());
+            }
+
+            //Iterate over all possible nodes, as the graph is complete in the case of a distance matrix
+            if (useEdges) {
+                expandStatesViaEdges(currentState, results, stateQueue, graph);
+            } else {
+                expandStatesViaMatrix(currentState, results, stateQueue, graph);
+            }
+        }
+    }
+
+    return results;
+}
+
+MatrixShortestPathContainer VehicleShortestPathCalculation::computeShortestPathsForGraph(Graph &graph) {
+    return computeShortestPathsForGraph(graph, false);
+}
+
+MatrixShortestPathContainer VehicleShortestPathCalculation::computeShortestPathsForGraph(Graph &graph, bool useEdges) {
+    std::vector<std::vector<uint>> results{graph.getNbNodes()};
+    for(size_t i = 0; i < graph.getNbNodes(); ++i) {
+        results[i].resize(graph.getNbNodes());
+        std::ranges::move(computeShortestPathsFromNode(graph, i, useEdges), results[i].begin());
+    }
+
+    return MatrixShortestPathContainer(results);
+}
+
+void VehicleShortestPathCalculation::computeAndUpdateShortestPathsForGraph(Graph &graph) {
+    computeAndUpdateShortestPathsForGraph(graph, false);
+}
+
+void VehicleShortestPathCalculation::computeAndUpdateShortestPathsForGraph(Graph &graph, bool useEdges) {
+    MatrixShortestPathContainer results = computeShortestPathsForGraph(graph, useEdges);
+    graph.setShortestSaevPaths(results.getDistanceMatrix());
+}
+
+ClosestDestinationsContainer
+VehicleShortestPathCalculation::getClosestPTNodesFromX(const Graph &graph, size_t startingNodeIdx) {
+    ClosestDestinationsContainer closestDestinationsContainer(startingNodeIdx);
+    for(size_t destinationIdx=0; destinationIdx < graph.getNbNodes(); ++destinationIdx) {
+        if(graph.getNbPTLines(destinationIdx) > 0) {
+            closestDestinationsContainer.addDestination(destinationIdx,
+                                                        graph.getShortestSAEVPath(startingNodeIdx, destinationIdx));
+        }
+    }
+    return closestDestinationsContainer;
+}
+
+void VehicleShortestPathCalculation::expandStatesViaMatrix(const VehiclePathState& currentState, std::vector<uint> &results,
+                                                           std::priority_queue<VehiclePathState, std::vector<VehiclePathState>, std::greater<>> &stateQueue,
+                                                           const Graph& graph) {
+    uint newDistance = INT16_MAX;
+    for(size_t i = 0; i < results.capacity(); ++i) {
+        newDistance = currentState.getInstant() + graph.getShortestSAEVPath(currentState.getNodeIndex(), i);
+        if(newDistance < results[i]) {
+            stateQueue.emplace(i, newDistance);
+            results[i] = newDistance;
+        }
+    }
+}
+
+void VehicleShortestPathCalculation::expandStatesViaEdges(const VehiclePathState &currentState, std::vector<uint> &results,
+                                                          std::priority_queue<VehiclePathState, std::vector<VehiclePathState>, std::greater<>> &stateQueue,
+                                                          const Graph& graph) {
+    uint newDistance = INT16_MAX;
+    for(const auto& edgeIndex : graph.getNode(currentState.getNodeIndex()).getOutgoingEdges()) {
+        const Edge& edge = graph.getEdge(edgeIndex);
+        newDistance = currentState.getInstant() + edge.getLength();
+        if(newDistance < results[edge.getEndNodeIdx()]) {
+            stateQueue.emplace(edge.getEndNodeIdx(), newDistance);
+            results[edge.getEndNodeIdx()] = newDistance;
+        }
+    }
+}
diff --git a/src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h b/src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
new file mode 100644
index 0000000000000000000000000000000000000000..6026714ec09245ae3bf31a5446c4c84ed63bf78f
--- /dev/null
+++ b/src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
@@ -0,0 +1,56 @@
+//
+// Created by romain on 16/07/24.
+//
+
+#ifndef GREEDYALGORITHM_VEHICLESHORTESTPATHCALCULATION_H
+#define GREEDYALGORITHM_VEHICLESHORTESTPATHCALCULATION_H
+
+
+#include "MatrixShortestPathContainer.h"
+#include "ClosestDestinationsContainer.h"
+#include "VehiclePathState.h"
+
+class Graph;
+class VehicleShortestPathCalculation {
+public:
+    /**
+     * Computes shortest paths from the given graph's distance matrix
+     * @param graph
+     */
+    static void computeAndUpdateShortestPathsForGraph(Graph &graph, bool useEdges);
+    /**
+     * Computes and returns an updated distance matrix from the given graph's distance matrix computed via Dijsktra's algorithm
+     * @param graph
+     * @return A matrix distances container
+     */
+    static MatrixShortestPathContainer computeShortestPathsForGraph(Graph &graph, bool useEdges);
+    /**
+     * Computes the shortest paths to all the nodes in the graph from the given starting node.
+     * If a node is unreachable its default distance is INT32_MAX. Distance from the starting node to itself is 0
+     * @param graph
+     * @param startingNodeIdx
+     * @return a vector indexed by all the nodes with the sortest path's duration from the starting node to the destination node index
+     */
+    static std::vector<uint> computeShortestPathsFromNode(Graph &graph, size_t startingNodeIdx, bool useEdges);
+    /**
+     * Returns an ordered set of closest PT SAV-compatible nodes from a given starting node index.
+     * Standard use case expects that the starting node index is a request origin
+     * @param graph
+     * @param startingNodeIdx
+     * @return An ordered set containing PT nodes accessible via SAV from the given starting node index
+     */
+    static ClosestDestinationsContainer getClosestPTNodesFromX(const Graph& graph, size_t startingNodeIdx);
+private:
+    static void expandStatesViaEdges(const VehiclePathState& currentState, std::vector<uint> &results,
+                                     std::priority_queue<VehiclePathState, std::vector<VehiclePathState>, std::greater<>> &stateQueue, const Graph& graph);
+    static void expandStatesViaMatrix(const VehiclePathState& currentState, std::vector<uint> &results,
+                                      std::priority_queue<VehiclePathState, std::vector<VehiclePathState>, std::greater<>> &stateQueue, const Graph& graph);
+
+    void computeAndUpdateShortestPathsForGraph(Graph &graph);
+
+    MatrixShortestPathContainer computeShortestPathsForGraph(Graph &graph);
+};
+#include "../../../instance/graph/Graph.h"
+
+
+#endif //GREEDYALGORITHM_VEHICLESHORTESTPATHCALCULATION_H
diff --git a/src/instance/Instance.cpp b/src/instance/Instance.cpp
index f650d9fefccee90a97d6a78e7600499338c6a954..835760311018ef77e780a56cf13065e1e4f15ca6 100644
--- a/src/instance/Instance.cpp
+++ b/src/instance/Instance.cpp
@@ -2,6 +2,25 @@
 // Created by rbernard on 22/01/24.
 //
 
+#include <iostream>
 #include "Instance.h"
 #include "graph/Graph.h"
-#include "requests/Request.h"
\ No newline at end of file
+#include "requests/Request.h"
+#include "../services/DatFile/DATRow.h"
+
+Instance::Instance(const std::string& graphFilePath,
+                   const std::string& requestsFilePath,
+                   const int vehicleCapacity): _graph(graphFilePath),_vehicleCapacity(vehicleCapacity)
+{
+    parseRequestsFromFile(requestsFilePath);
+}
+
+void Instance::parseRequestsFromFile(const std::string &requestsFilePath) {
+    std::ifstream infile(requestsFilePath);
+    assertm(!infile.fail(), "Failed to open the given file");
+    DATRow currentRow = DATRow(',');
+    std::string currentLine;
+    while(infile >> currentRow && !currentRow[0].starts_with('#')) {
+        _requests.emplace_back(currentRow, _graph);
+    }
+}
diff --git a/src/instance/Instance.h b/src/instance/Instance.h
index ebf12ec954cbb66ae326dd7be940b108367a2547..60ea88340baa28d51ee9d824227c399c56f13e36 100644
--- a/src/instance/Instance.h
+++ b/src/instance/Instance.h
@@ -4,18 +4,29 @@
 
 #ifndef GREEDYALGORITHM_INSTANCE_H
 #define GREEDYALGORITHM_INSTANCE_H
+#include <utility>
 #include <vector>
 #include "requests/Request.h"
 #include "graph/Graph.h"
+#include "SAEVehicle.h"
 
 class Instance {
 private:
-    std::vector<Request> requests;
-    Graph graph;
+
+    std::vector<Request> _requests;
+    Graph _graph;
+    int _vehicleCapacity;
 
 public:
-    [[nodiscard]] Graph const & getGraph() const { return graph;}
-    [[nodiscard]] std::vector<Request> const & getRequests() const { return requests;}
+    Instance(const std::string &graphFilePath, const std::string &requestsFilePath, const int vehicleCapacity);
+    Instance(const std::vector<Request> &requests, Graph graph, const int vehicleCapacity) : _requests(
+            requests), _graph(std::move(graph)), _vehicleCapacity(vehicleCapacity) {}
+
+    [[nodiscard]] Graph const & getGraph() const { return _graph;}
+    [[nodiscard]] std::vector<Request> const & getRequests() const { return _requests;}
+    [[nodiscard]] int getVehicleCapacity() const { return _vehicleCapacity; }
+
+    void parseRequestsFromFile(const std::string &basicString);
 };
 
 
diff --git a/src/instance/SAEVehicle.cpp b/src/instance/SAEVehicle.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bac525f88c3576ca6c25bb426e8ba4f9638bdf8a
--- /dev/null
+++ b/src/instance/SAEVehicle.cpp
@@ -0,0 +1,5 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#include "SAEVehicle.h"
diff --git a/src/instance/SAEVehicle.h b/src/instance/SAEVehicle.h
new file mode 100644
index 0000000000000000000000000000000000000000..c7e5ceb585c877001e53f5471aa9f2022719b317
--- /dev/null
+++ b/src/instance/SAEVehicle.h
@@ -0,0 +1,24 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#ifndef GREEDYALGORITHM_SAEVEHICLE_H
+#define GREEDYALGORITHM_SAEVEHICLE_H
+
+#include "../utils/Constants.h"
+
+class SAEVehicle {
+private:
+    static const int _capacity{Constants::VEHICLE_CAPACITY};
+
+public:
+    SAEVehicle() = default;
+//    explicit SAEVehicle(const int capacity) : _capacity(capacity) {}
+
+    [[nodiscard]] static int getCapacity() {
+        return _capacity;
+    }
+};
+
+
+#endif //GREEDYALGORITHM_SAEVEHICLE_H
diff --git a/src/instance/graph/Edge.cpp b/src/instance/graph/Edge.cpp
index 5c8a04369f29bbaea58962182c0b2187175ee704..c9be6dbf2b6d6b635654c4de8a425aa423c55b7e 100644
--- a/src/instance/graph/Edge.cpp
+++ b/src/instance/graph/Edge.cpp
@@ -4,32 +4,26 @@
 
 #include "Edge.h"
 
-int Edge::getNodeStart() const {
-    return _start;
+size_t Edge::getStartNodeIdx() const {
+    return _startNodeIdx;
 }
 
-void Edge::setNodeStart(int start) {
-    Edge::_start = start;
+void Edge::setStartNodeIdx(size_t startNodeIdx) {
+    Edge::_startNodeIdx = startNodeIdx;
 }
 
-int Edge::getNodeEnd() const {
-    return _end;
+size_t Edge::getEndNodeIdx() const {
+    return _endNodeIdx;
 }
 
-void Edge::setNodeEnd(int end) {
-    Edge::_end = end;
+void Edge::setEndNodeIdx(size_t endNodeIdx) {
+    Edge::_endNodeIdx = endNodeIdx;
 }
 
-double Edge::getLength() const {
+uint 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;
+void Edge::setLength(uint length) {
+    Edge::_length = length;
 }
diff --git a/src/instance/graph/Edge.h b/src/instance/graph/Edge.h
index a7126a08094e1301ded8ecf0590db606b1949214..f8e5498c0457caa94b6560f481bc1cd6362f0f71 100644
--- a/src/instance/graph/Edge.h
+++ b/src/instance/graph/Edge.h
@@ -7,27 +7,26 @@
 
 
 #include "Node.h"
+#include "../../utils/Globals.h"
 
 class Edge {
 private:
-    int _start;
-    int _end;
-    double _length;
+    size_t _startNodeIdx{};
+    size_t _endNodeIdx{};
+    uint _length{};
 
 public:
-    int getNodeStart() const;
+    [[nodiscard]] size_t getStartNodeIdx() const;
+    void setStartNodeIdx(size_t startNodeIdx);
 
-    void setNodeStart(int start);
+    [[nodiscard]] size_t getEndNodeIdx() const;
+    void setEndNodeIdx(size_t endNodeIdx);
 
-    int getNodeEnd() const;
+    [[nodiscard]] uint getLength() const;
+    void setLength(uint length);
 
-    void setNodeEnd(int end);
-
-    double getLength() const;
-
-    void setLength(double d);
-
-    Edge(int start, int end, double length);
+    Edge() = default; //only used to reserve memory space for a variable in some instances
+    Edge(size_t startNodeIdx, size_t endNodeIdx, uint length) : _startNodeIdx(startNodeIdx), _endNodeIdx(endNodeIdx), _length(length) {}
 };
 
 
diff --git a/src/instance/graph/Graph.cpp b/src/instance/graph/Graph.cpp
index 955625188371140260331ea61f3ce0388be85cba..2454d28107f1491f5f7ac8f5a55aaef5a1948bae 100644
--- a/src/instance/graph/Graph.cpp
+++ b/src/instance/graph/Graph.cpp
@@ -2,10 +2,10 @@
 // Created by rbernard on 22/01/24.
 //
 
-#ifdef DEBUG
-#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
+#ifdef DEBUG_GRAPH
+#define DEBUG_GRAPH_MSG(str) do { std::cout << "[GRAPH] " + str << std::endl; } while( false )
 #else
-#define DEBUG_MSG(str) do { } while ( false )
+#define DEBUG_GRAPH_MSG(str) do { } while ( false )
 #endif
 
 #include <sstream>
@@ -16,12 +16,7 @@
 #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);
-    }
+const std::vector<Line>& Graph::addLine(const Line &line) {
     //Add transit line to transit lines vector
     transitLines.push_back(line);
     return transitLines;
@@ -31,14 +26,19 @@ Graph::Graph(const std::string& nodesFilePath, const std::string& edgesFilePath,
 
     //Nodes instantiation
     std::ifstream nodesFile(nodesFilePath);
+    assertm(!nodesFile.fail(), "Failed to open the given file");
     std::cout << "Nodes instantiation" << std::endl;
     for(auto& row: CSVRange(nodesFile))
     {
         parseNodeRow(row);
     }
 
+    shortestSAEVPaths.reserve(nodesVector.size());
+    for(std::vector<uint> yValues : shortestSAEVPaths) { yValues.reserve(nodesVector.size()); }
+
     //Edges instantiation
     std::ifstream edgesFile(edgesFilePath);
+    assertm(!edgesFile.fail(), "Failed to open the given file");
     std::cout << "Edges instantiation" << std::endl;
     for(auto& row: CSVRange(edgesFile))
     {
@@ -48,25 +48,25 @@ Graph::Graph(const std::string& nodesFilePath, const std::string& edgesFilePath,
     //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);
+    assertm(!ptLinesFile.fail(), "Failed to open the given file");
     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);
+            parseLineRandomizedSchedule(row, rng);
         }
     }
+    linkAllPTNodes();
     std::cout << "test is done" << std::endl;
 }
 
 Graph::Graph(const std::string& datFilePath) {
     std::ifstream infile(datFilePath);
+    assertm(!infile.fail(), "Failed to open the given file");
     DATRow currentRow = DATRow(',');
-    std::string currentLine;
 
     //-- Read params
     infile >> currentRow;
@@ -74,7 +74,7 @@ Graph::Graph(const std::string& datFilePath) {
     // Seeded random number generator
     infile >> currentRow;
     unsigned long rngSeed;
-    std::from_chars(currentRow[1].data(), currentRow[1].data() + currentRow[1].length(), rngSeed);
+    std::from_chars(currentRow[0].data(), currentRow[0].data() + currentRow[0].length(), rngSeed);
     auto rng = std::mt19937(rngSeed);
     //-- End of params
 
@@ -86,88 +86,124 @@ Graph::Graph(const std::string& datFilePath) {
     }
     //-- End of nodes
 
-    //-- Read Edges
-    std::cout << currentRow.toString() << std::endl;
-    while(infile >> currentRow && !currentRow[0].starts_with('#')) {
-        this->parseEdgeRow(currentRow);
+    //Node links (edges or matrix)
+    std::string lowercaseRow = currentRow.toString();
+    std::ranges::transform(lowercaseRow, lowercaseRow.begin(), ::tolower);
+    if(lowercaseRow.starts_with("#edges")) {
+        //-- Read Edges
+        std::cout << currentRow.toString() << std::endl;
+        while (infile >> currentRow && !currentRow[0].starts_with('#')) {
+            this->parseEdgeRow(currentRow);
+        }
+        //-- End of edges
+    } else if (lowercaseRow.starts_with("#matrix")) {
+        //-- Read Distance matrix
+        std::cout << currentRow.toString() << std::endl;
+        this->parseDistanceMatrix(infile, currentRow);
+        //-- End of edges
+    }
+
+    lowercaseRow = currentRow.toString();
+    std::ranges::transform(lowercaseRow, lowercaseRow.begin(), ::tolower);
+    if(lowercaseRow.starts_with("#depot")) {
+        infile >> currentRow;
+        std::from_chars(currentRow[0].data(), currentRow[0].data() + currentRow[0].length(), _depotNodeIdx);
     }
-    //-- 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);
+    lowercaseRow = currentRow.toString();
+    std::ranges::transform(lowercaseRow, lowercaseRow.begin(), ::tolower);
+    if(lowercaseRow.starts_with("#pt line fixed")) {
+        while(infile >> currentRow && !currentRow[0].starts_with('#')) {
+            this->parseLineFixedSchedule(infile, currentRow);
+        }
+    } else {
+        while(infile >> currentRow && !currentRow[0].starts_with('#')) {
+            this->parseLineRandomizedSchedule(currentRow, rng);
+        }
     }
+
+    //Make links once all lines are created to prevent stale refs
+    this->linkAllPTNodes();
 }
 
 namespace fs = std::filesystem;
-void Graph::exportGraphToFiles(fs::path exportFolderPath) {
+void Graph::exportGraphToFile(const 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)
+    std::ofstream outfileGraph(exportFolderPath.string() + "graph.txt", std::ofstream::out | std::ofstream::trunc); //open and clear file if it already existed
+    outfileGraph << "#Nodes format : status (work, leisure, residential),x,y" << std::endl;
+    for(auto const& node : this->nodesVector)
     {
-        outfileNodes << node.getX() << " " << node.getY() << std::endl;
+        outfileGraph << node.getStatus() << "," << 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;
+    if(!edgesVector.empty()) {
+        outfileGraph << "#Edges format : node_in,node_out,length" << std::endl;
+        for (auto const &edge: this->edgesVector) {
+            outfileGraph << edge.getStartNodeIdx() << "," << edge.getEndNodeIdx() << "," << 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();
+    //Matrix
+    if(!shortestSAEVPaths.empty()) {
+        outfileGraph << "#Matrix" << std::endl;
+        std::stringstream lineStringStream;
+        for (auto &matrixLine: this->shortestSAEVPaths) {
+            std::ranges::copy(matrixLine.begin(), matrixLine.end() - 1,
+                              std::ostream_iterator<int>(lineStringStream, ","));
+            lineStringStream << matrixLine.back();
+            outfileGraph << lineStringStream.rdbuf() << std::endl;
         }
-        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())
-            {
+    //Transit lines
+    if(!getPTLines().empty()) {
+        outfileGraph << "#PT Lines" << std::endl;
+        for (auto const &ptline: this->transitLines) {
+            //Print nodes in order on one line
+            std::stringstream ossNodes;
+            std::vector<int> lineNodesVector = ptline.getNodes();
+            if (!lineNodesVector.empty()) {
                 // Convert all but the last element to avoid a trailing ","
-                std::copy(schedule.begin(), schedule.end()-1,
-                          std::ostream_iterator<int>(ossSchedule, " "));
+                std::copy(lineNodesVector.begin(), lineNodesVector.end() - 1,
+                          std::ostream_iterator<int>(ossNodes, ","));
 
                 // Now add the last element with no delimiter
-                ossSchedule << schedule.back();
+                ossNodes << lineNodesVector.back();
+            }
+            std::cout << ossNodes.view() << std::endl;
+            outfileGraph << ossNodes.rdbuf() << std::endl;
+            ossNodes.clear();
+
+            //Reuse string stream to print schedules line by line
+            std::stringstream ossSchedule;
+            for (auto &schedule: ptline.getTimetables()) {
+                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;
+                outfileGraph << ossSchedule.rdbuf() << std::endl;
+                ossSchedule.str("");
+                ossSchedule.clear();
             }
-            std::cout << ossSchedule.view() << std::endl;
-            outfilePT << ossSchedule.str() << std::endl;
-            ossSchedule.clear();
         }
-        outfilePT << "#PT line end" <<std::endl;
     }
-    outfilePT.close();
+    outfileGraph.close();
     std::cout << "results of graph validations : " << this->check() << std::endl;
 }
 
 bool Graph::check() {
     bool checkResult = true;
-    for(auto& transitLine : this->transitLines)
+    for(auto const& transitLine : this->transitLines)
     {
         checkResult &= transitLine.check();
     }
@@ -177,15 +213,15 @@ bool Graph::check() {
 
 bool Graph::checkLineToNodeLinks() {
     int nodeIndexFromLine;
-    Node* nodeFromGraph; //Forced to init here
+    Node const* nodeFromGraph; //Forced to init here
 
     bool checkResult = true;
-    for(auto& node : nodesVector)
+    for(auto const& node : nodesVector)
     {
-        for(auto& lineStop : node.getPTLinesSet())
+        for(auto const& lineStop : node.getPTLinesSet())
         {
             nodeIndexFromLine = lineStop.getLineRef().getNode(lineStop.getStopIndex());
-            nodeFromGraph = &this->nodesVector.at(nodeIndexFromLine);
+            nodeFromGraph = &this->nodesVector[nodeIndexFromLine];
             checkResult &= *nodeFromGraph == node;
         }
     }
@@ -204,7 +240,7 @@ void Graph::parseNodeRow(const DATRow& row)
         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);
+        DEBUG_GRAPH_MSG("Created new node " << x << " " << y << " with status = " << status);
     }
 }
 
@@ -220,17 +256,15 @@ void Graph::parseEdgeRow(const DATRow& row)
         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);
+        DEBUG_GRAPH_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)
+void Graph::parseLineRandomizedSchedule(const DATRow& row, std::mt19937 rng)
 {
-    int startTime, endTime, frequency, currentNodeIdx;
+    int startTime, endTime, frequency, minTravelTime, maxTravelTime, currentNodeIdx;
     //add nodes for the line
-    Line newLine = Line();
+    Line newLine{};
     //Give it an ID
     //TODO : use proper IDs in parsing for line names
     newLine.setLineId(std::to_string(this->transitLines.size()));
@@ -240,7 +274,11 @@ void Graph::parseLineRandomizedSchedule(const DATRow& row, std::mt19937 rng,
     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
+    std::from_chars(row[3].data(), row[3].data() + row[3].size(), minTravelTime);
+    std::from_chars(row[4].data(), row[4].data() + row[4].size(), maxTravelTime);
+    std::uniform_int_distribution<uint32_t> travelTimeDistribution(minTravelTime,maxTravelTime);
+
+    int currentTime = startTime;
     while(currentTime + frequency < endTime)
     {
         timeTable.push_back(currentTime + frequency);
@@ -255,31 +293,158 @@ void Graph::parseLineRandomizedSchedule(const DATRow& row, std::mt19937 rng,
     }
 
     //Create subsequent timetables according to preceding timetable and travel time
-    for(int i = 1; i < newLine.getNodes().size(); ++i)
+    for(size_t 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
+        uint travelTime = travelTimeDistribution(rng);
         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);
+        newTimetable.reserve(precedingTimeTable.size()); //Reserve to improve foreach efficiency
+        for(int const & it : precedingTimeTable) {
+            newTimetable.emplace_back(it + travelTime);
         }
         newLine.addTimetable(newTimetable);
         newTimetable.clear();
     }
+    //Check for errors
+    if(newLine.check()) {
+        this->addLine(newLine);
+        DEBUG_GRAPH_MSG("Created new line with randomized schedule");
+    }
+}
 
-    this->addLine(newLine);
+void Graph::parseLineFixedSchedule(std::ifstream& infile, DATRow& row) {
+    Line newLine = Line();
 
-    DEBUG_MSG("Created new line with nodes");
+    // First line consists of the list of node indexes, add them all to the new line
+    uint currentNodeIdx;
+    for(uint i = 0; i < row.size(); ++i)
+    {
+        std::from_chars(row[i].data(), row[i].data() + row[i].size(), currentNodeIdx);
+        newLine.addNode(currentNodeIdx);
+    }
 
+    // Read schedule until hitting an empty line, used to separate individual lines to parse
+    int scheduledTime;
+    std::vector<std::vector<int>> newSchedules{newLine.size()};
+    while(infile >> row && !row[0].empty()) {
+        assertm(row.size() == newLine.size(), "Schedule size doesn't correspond to the number of nodes");
+        // Add the time to the appropriate station's schedule vector
+        for(uint stationIdx = 0; stationIdx < row.size(); ++stationIdx) {
+            std::from_chars(row[stationIdx].data(), row[stationIdx].data() + row[stationIdx].size(), scheduledTime);
+            newSchedules[stationIdx].emplace_back(scheduledTime);
+        }
+    }
+
+    //Add properly formatted timetables to the new line in station order
+    for(const std::vector<int>& stationSchedule : newSchedules) {
+        newLine.addTimetable(stationSchedule);
+    }
+
+    //Check for errors
+    if(newLine.check()) {
+        this->addLine(newLine);
+        DEBUG_GRAPH_MSG("Created new line with fixed schedule");
+    }
 }
 
-void Graph::createAndAddEdge(int edgeStartNodeIndex, int edgeEndNodeIndex, double length) {
+void Graph::createAndAddEdge(size_t edgeStartNodeIndex, size_t edgeEndNodeIndex, double length) {
     edgesVector.emplace_back(edgeStartNodeIndex, edgeEndNodeIndex, length);
 
-    Node entryNode = nodesVector.at(edgeStartNodeIndex);
-    entryNode.getOutgoingEdges().emplace_back(edgesVector.size() - 1);
+    Node& entryNode = nodesVector[edgeStartNodeIndex];
+    entryNode.emplaceBackOutgoingEdge(edgesVector.size() - 1);
+
+    Node& exitNode = nodesVector[edgeEndNodeIndex];
+    exitNode.emplaceBackIncomingEdge(edgesVector.size() - 1);
+}
+
+void Graph::parseDistanceMatrix(std::ifstream &infile, DATRow currentRow) {
+    int intVal;
+    while (infile >> currentRow && !currentRow[0].starts_with('#')) {
+        auto& matrixLine = shortestSAEVPaths.emplace_back();
+        for(int i = 0; i < currentRow.size(); ++i) {
+            std::from_chars(currentRow[i].data(), currentRow[i].data() + currentRow[i].size(), intVal);
+            matrixLine.emplace_back(intVal < 0 ? INT16_MAX : intVal);
+        }
+    }
+}
+
+size_t Graph::getDepotNodeIdx() const {
+    return _depotNodeIdx;
+}
+
+void Graph::setDepotNodeIdx(size_t depotNodeIdx) {
+    _depotNodeIdx = depotNodeIdx;
+}
+
+const std::vector<std::vector<uint>> &Graph::getShortestSaevPaths() const {
+    return shortestSAEVPaths;
+}
+
+void Graph::setShortestSaevPaths(const std::vector<std::vector<uint>> &shortestSaevPaths) {
+    shortestSAEVPaths = shortestSaevPaths;
+}
+
+void Graph::emplaceBackClosestStation(size_t nodeIdx, size_t stationNodeIdx) {
+    nodesVector[nodeIdx].emplaceBackClosestStation(stationNodeIdx);
+}
+
+const size_t Graph::getNbClosestStations(size_t nodeIdx) {
+    return nodesVector[nodeIdx].getBestStationsNodeIdxVector().size();
+}
+
+void Graph::computeAndUpdateClosestStationsForNode(size_t nodeIdx) {
+    ClosestDestinationsContainer closestPTNodes = VehicleShortestPathCalculation::getClosestPTNodesFromX(*this, nodeIdx);
+    for(const VehicleDestination& closestDestination : closestPTNodes.getOrderedDestinations()) {
+        nodesVector[nodeIdx].emplaceBackClosestStation(closestDestination.getDestinationNodeIdx());
+        if(nodesVector[nodeIdx].getBestStationsNodeIdxVector().size() == Constants::MAX_CLOSEST_STATIONS_CANDIDATES) {
+            break;
+        }
+    }
+}
+
+void Graph::computeAndUpdateClosestStations() {
+    for(size_t i = 0; i < nodesVector.size(); ++i)
+        computeAndUpdateClosestStationsForNode(i);
+}
+
+void Graph::computeAndUpdateShortestTransitPaths() {
+    TransitShortestPathContainer shortestPathsContainer(getNbNodes());
+    for(auto& ptLine : getPTLines()) {
+        for(size_t i = 0; i < ptLine.size(); ++i) {
+            for (auto& startingTime: ptLine.getTimetable(i)) {
+                const TransitStateContainer &results = TransitShortestPathPrecompute::executeAlgorithm(*this,ptLine.getNode(i),startingTime);
+                shortestPathsContainer.addShortestPathCollection(ptLine.getNode(i), startingTime, getNbNodes(), results);
+            }
+        }
+    }
+    transitShortestPaths = shortestPathsContainer;
+}
+
+/**
+ * Clears every node's set of lines (to remove any eventual stale reference)
+ * then links every line's station to its referred node
+ */
+void Graph::linkAllPTNodes() {
+    for(auto& node : nodesVector) {
+        node.clearPTLineSet();
+    }
+    for(auto& line : getPTLines()) {
+        for(size_t i = 0; i < line.size(); ++i) {
+            nodesVector[line.getNode(i)].addBusLine(line, i);
+        }
+    }
+}
+
+const TransitShortestPathContainer &Graph::getTransitShortestPaths() const {
+    return transitShortestPaths;
+}
+
+const std::pair<size_t, std::vector<TransitShortestPath>> &
+Graph::getShortestTransitPathsFrom(size_t startNodeIndex, uint earliestStartInstant) const {
+    return transitShortestPaths.getShortestPathsFrom(startNodeIndex, earliestStartInstant);
+}
 
-    Node exitNode = nodesVector.at(edgeEndNodeIndex);
-    exitNode.getIncomingEdges().emplace_back(edgesVector.size() - 1);
+TransitShortestPath
+Graph::getShortestTransitPathToYFromTime(size_t startNodeIndex, uint earliestStartInstant, size_t goalNode) const {
+    return transitShortestPaths.getShortestPathToYFromTime(startNodeIndex, earliestStartInstant, goalNode);
 }
diff --git a/src/instance/graph/Graph.h b/src/instance/graph/Graph.h
index 9ad45fe3001ecb9709ddea97659d2438e87b10c7..2d7bd8a53309134d184008216d7ebef8f3c6e347 100644
--- a/src/instance/graph/Graph.h
+++ b/src/instance/graph/Graph.h
@@ -13,6 +13,11 @@
 
 #include "Node.h"
 #include "Edge.h"
+#include "../../utils/Globals.h"
+#include "../../utils/Constants.h"
+#include "../../algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h"
+#include "../../algorithm/ShortestPath/Transit/TransitShortestPathContainer.h"
+#include "../../algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h"
 
 class DATRow;
 class Graph {
@@ -20,6 +25,9 @@ private:
     std::vector<Node> nodesVector; //The full list of nodes created in the graph
     std::vector<Edge> edgesVector;
     std::vector<Line> transitLines;
+    std::vector<std::vector<uint>> shortestSAEVPaths;
+    TransitShortestPathContainer transitShortestPaths = TransitShortestPathContainer(0);
+    size_t _depotNodeIdx{0}; //Index of the depot node, defaults to 0, the first node created in the graph
 
     /**
      * For every LineStop on every node of the graph, verify the node returned by looking into LineStop->Line(stopIdx)
@@ -27,51 +35,89 @@ private:
      * @return True if @this is properly referenced in the Line object at the expected index
      */
     bool checkLineToNodeLinks();
+    /**
+     * Parses and adds a node from the given row object to the graph.<br>
+     * Format : status,x_coordinate,y_coordinate
+     * @param row row extracted from the input file
+     */
     void parseNodeRow(const DATRow& row);
+    /**
+     * Parses and adds an edge from the given row object to the graph.<br>
+     * Format : start_node_idx,end_node_idx,length
+     * @param row row extracted from the input file
+     */
     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);
+
+    /**
+     * Parses and adds a line to the graph from a given row object and reusing the given seeded rng engine.
+     * Uses the format : <br>
+     * frequency,start_time,end_time,min_travel_time,max_travel_time,node_0, ..., node_n
+     * @param row row containing the necessary info to generate a random line
+     * @param rng seeded random number generator
+     */
+    void parseLineRandomizedSchedule(const DATRow &row, std::mt19937 rng);
+
+    /**
+     * Parses and adds a line to the graph from the given row in given the following format : <br>
+     * node_0_idx, ... node_n_idx <br>
+     * node_0_pass_0_time, ... node_n_pass_0_time <br>
+     * ... <br>
+     * node_0_pass_m_time, ... node_n_pass_m_time <br>
+     * <br>
+     * /!\ each transit line to parse ends with an empty line
+     *
+     * @param infile input file stream, necessary to iterate over multiple lines in the function
+     * @param row the current DATrow object we reuse from other parsing functions
+     */
+    void parseLineFixedSchedule(std::ifstream& infile, DATRow& row);
 
 public:
     [[nodiscard]] const std::vector<Node> &getNodesVector() const {
         return nodesVector;
     }
 
-    [[nodiscard]] const Node &getNode(int nodeIndex) const {
-        return nodesVector.at(nodeIndex);
+    [[nodiscard]] const Node &getNode(size_t nodeIndex) const {
+        return nodesVector[nodeIndex];
     }
 
     [[nodiscard]] size_t getNbNodes() const {
         return nodesVector.size();
     }
 
-    [[nodiscard]] std::vector<LineStop> getPTLinesSet(int nodeIndex) const {
-        return nodesVector.at(nodeIndex).getPTLinesSet();
+    [[nodiscard]] const std::vector<LineStop> & getPTLinesSet(size_t nodeIndex) const {
+        return nodesVector[nodeIndex].getPTLinesSet();
     }
 
-    [[nodiscard]] size_t getNbPTLines(int nodeIndex) const {
-        return nodesVector.at(nodeIndex).getPTLinesSet().size();
+    [[nodiscard]] size_t getNbPTLines(size_t nodeIndex) const {
+        return nodesVector[nodeIndex].getPTLinesSet().size();
     }
 
+    [[nodiscard]] size_t getDepotNodeIdx() const;
+    void setDepotNodeIdx(size_t depotNodeIdx);
+
     /**
      * @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();
+    /**
+     * @return Const ref to the edge at the given index
+     */
+    [[nodiscard]] const Edge &getEdge(size_t edgeIndex) const {
+        return edgesVector[edgeIndex];
     }
 
-    [[nodiscard]] size_t getNbOutgoingEdges(int nodeIndex) const {
-        return nodesVector.at(nodeIndex).getOutgoingEdges().size();
+    [[nodiscard]] size_t getNbIncomingEdges(size_t nodeIndex) const {
+        return nodesVector[nodeIndex].getIncomingEdges().size();
     }
 
+    [[nodiscard]] size_t getNbOutgoingEdges(size_t nodeIndex) const {
+        return nodesVector[nodeIndex].getOutgoingEdges().size();
+    }
 
-    [[nodiscard]] size_t getNbEdges(int nodeIndex) const {
-        return nodesVector.at(nodeIndex).getIncomingEdges().size() + nodesVector.at(nodeIndex).getOutgoingEdges().size();
+    [[nodiscard]] size_t getNbEdges(size_t nodeIndex) const {
+        return nodesVector[nodeIndex].getIncomingEdges().size() + nodesVector[nodeIndex].getOutgoingEdges().size();
     }
 
     /**
@@ -86,7 +132,7 @@ public:
      * @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) {
+    const std::vector<Edge>& addEdge(Edge const& edge) {
         edgesVector.push_back(edge);
         return edgesVector;
     }
@@ -96,7 +142,7 @@ public:
      * @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) {
+    const std::vector<Node>& addNode(Node const& node) {
         nodesVector.push_back(node);
         return nodesVector;
     }
@@ -116,6 +162,7 @@ public:
      * @param edgeFilePath The file containing edge data (format for each line : start_node_index,end_node_index,edge_length)
      * @param ptLineFilePath
      */
+    [[deprecated("Replaced with the single file syntax for creating a graph via file")]]
     Graph(const std::string& nodeFilePath, const std::string& edgeFilePath, const std::string& ptLineFilePath);
 
     /**
@@ -123,13 +170,13 @@ public:
      * @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);
+    const std::vector<Line>& addLine(const 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);
+    void exportGraphToFile(const std::filesystem::path& exportFolderPath);
 
     /**
      * Executes defined check functions on every node and transit line in the graph
@@ -143,7 +190,37 @@ public:
      * @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);
+    void createAndAddEdge(size_t edgeStartNodeIndex, size_t edgeEndNodeIndex, double length);
+
+    [[nodiscard]] const std::vector<std::vector<uint>> &getShortestSaevPaths() const;
+
+    void setShortestSaevPaths(const std::vector<std::vector<uint>> &shortestSaevPaths);
+
+    [[nodiscard]] uint getShortestSAEVPath(size_t startingNodeIdx, size_t destinationNodeIdx) const { return shortestSAEVPaths[startingNodeIdx][destinationNodeIdx]; }
+
+    void parseDistanceMatrix(std::ifstream &infile, DATRow currentRow);
+
+    void emplaceBackClosestStation(size_t nodeIdx, size_t stationNodeIdx);
+
+    const size_t getNbClosestStations(size_t nodeIdx);
+
+    void computeAndUpdateShortestPathsMatrix() {
+        VehicleShortestPathCalculation::computeAndUpdateShortestPathsForGraph(*this, false);
+    }
+
+    void computeAndUpdateShortestPathsMatrix(bool useEdges) {
+        VehicleShortestPathCalculation::computeAndUpdateShortestPathsForGraph(*this, useEdges);
+    }
+
+    void computeAndUpdateClosestStationsForNode(size_t nodeIdx);
+    void computeAndUpdateClosestStations();
+    void computeAndUpdateShortestTransitPaths();
+
+    [[nodiscard]] const TransitShortestPathContainer &getTransitShortestPaths() const;
+    const std::pair<size_t, std::vector<TransitShortestPath>>& getShortestTransitPathsFrom(size_t startNodeIndex, uint earliestStartInstant) const;
+    TransitShortestPath getShortestTransitPathToYFromTime(size_t startNodeIndex, uint earliestStartInstant, size_t goalNode) const;
+
+    void linkAllPTNodes();
 };
 
 
diff --git a/src/instance/graph/Line.cpp b/src/instance/graph/Line.cpp
index a07e3074e2ad791c34d496987c3ed0b34e49e842..48b94f0750a5f63cb559953599a509db94b7ea3a 100644
--- a/src/instance/graph/Line.cpp
+++ b/src/instance/graph/Line.cpp
@@ -13,7 +13,8 @@ bool Line::checkSchedules() const{
     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;
+    int expectedScheduleSize = !_timetables.empty() ? _timetables[0].size() : 0;
+    checkResult &= expectedScheduleSize > 0;
     for(auto& schedule : _timetables)
     {
         precedingTimeStep = 0; //reinit first timestep to 0
diff --git a/src/instance/graph/Line.h b/src/instance/graph/Line.h
index c9bc6f73b0c39cfad62dbbcc9739019a187e14bb..fb2c54b7d5b6bb2ec02ffb61e39a04ca5e84a661 100644
--- a/src/instance/graph/Line.h
+++ b/src/instance/graph/Line.h
@@ -10,6 +10,7 @@
 #include <vector>
 #include <string>
 #include "../../utils/SearchAlgorithms.h"
+#include "../../utils/Globals.h"
 
 class Line {
 private:
@@ -21,15 +22,15 @@ public:
 
     [[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);};
+    [[nodiscard]] const std::vector<int>& getNodes() const { return _nodes;};
+    [[nodiscard]] int getNode(size_t index) const { return _nodes[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;};
+    [[nodiscard]] const std::vector<int>& getTimetable(size_t pos) const{ return _timetables[pos];};
+    [[nodiscard]] const 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);};
+    void setTimetable(size_t 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)
@@ -37,8 +38,17 @@ public:
      * @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);
+    [[nodiscard]] std::vector<int>::const_iterator findNextScheduledPassage(size_t stationIdx, uint instant) const {
+        return SearchAlgorithms<int>::findNextSortedValue(_timetables[stationIdx], instant);
+    }
+    /**
+     * 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 findNextScheduledPassageIdx(size_t stationIdx, uint instant) const {
+        return SearchAlgorithms<int>::findNextSortedValueIdx(_timetables[stationIdx], instant);
     }
     /**
      * Returns the instant for the given station at a given schedule position, O(1)
@@ -46,10 +56,10 @@ public:
      * @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]] int getInstant(size_t stationIdx, size_t scheduleIdx) const { return _timetables[stationIdx][scheduleIdx]; }
 
     [[nodiscard]] size_t size() const {return _nodes.size();}
-    [[nodiscard]] size_t scheduleSize() const {return _timetables.empty() ? 0 : _timetables.at(0).size();}
+    [[nodiscard]] size_t scheduleSize() const {return _timetables.empty() ? 0 : _timetables[0].size();}
 
     [[nodiscard]] bool check() const;
     [[nodiscard]] bool checkSchedules() const;
diff --git a/src/instance/graph/LineStop.h b/src/instance/graph/LineStop.h
index 9d15019cab6313ceab02cec3756cb18d001c4202..44c4093d2edab5a04e34e800d210618a7f448e7d 100644
--- a/src/instance/graph/LineStop.h
+++ b/src/instance/graph/LineStop.h
@@ -12,18 +12,34 @@
 
 class LineStop {
 private:
-    Line _lineRef; //reference to the line
+    const 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 {
+    [[nodiscard]] const 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); }
+    [[nodiscard]] size_t findNextScheduledPassageIdx(uint instant) const {
+        return _lineRef.findNextScheduledPassageIdx(_stopIndex, instant);
+    }
+    [[nodiscard]] size_t findNextScheduledPassageIdx(size_t stationIdx, uint instant) const {
+        return _lineRef.findNextScheduledPassageIdx(stationIdx, instant);
+    }
+    [[nodiscard]] std::vector<int>::const_iterator findNextScheduledPassage(uint instant) const {
+        return _lineRef.findNextScheduledPassage(_stopIndex, instant);
+    }
+    [[nodiscard]] std::vector<int>::const_iterator findNextScheduledPassage(size_t stationIdx, uint instant) const {
+        return _lineRef.findNextScheduledPassage(stationIdx, instant);
+    }
+
+    [[nodiscard]] size_t getNodeIndex() const
+    {
+        return _lineRef.getNode(_stopIndex);
+    }
 
     /**
      * @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
@@ -61,10 +77,8 @@ public:
 
     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;
+    [[nodiscard]] const std::vector<int>& getSchedule() const {
+        return _lineRef.getTimetable(_stopIndex);
     }
 };
 
diff --git a/src/instance/graph/Node.cpp b/src/instance/graph/Node.cpp
index 6b6f7d99ae1a66a07847edca73f086d258a4b29d..0d8ead678d22a7d2d6a69ddf97aad0eab9f87e9b 100644
--- a/src/instance/graph/Node.cpp
+++ b/src/instance/graph/Node.cpp
@@ -13,7 +13,7 @@ Node::Node() {
     _y = -1;
 }
 
-bool Node::isPTNode() {
+bool Node::isPTNode() const {
     return &_ptLines == nullptr || _ptLines.empty();
 }
 
@@ -39,7 +39,7 @@ bool Node::operator!=(const Node &rhs) const {
            || _status != rhs.getStatus();
 }
 
-Status  Node::statusFromString(std::string from) {
+Status  Node::statusFromString(const std::string& from) {
     auto pair = stringToStatusMap.find(from);
     if (pair != stringToStatusMap.end()) {
         return pair->second;
@@ -47,3 +47,27 @@ Status  Node::statusFromString(std::string from) {
         return Status::work;
     }
 }
+
+void Node::emplaceBackClosestStation(size_t closestStationIdx) {
+    _bestStationsNodeIdxVector.emplace_back(closestStationIdx);
+}
+
+const std::vector<size_t> & Node::getBestStationsNodeIdxVector() const {
+    return _bestStationsNodeIdxVector;
+}
+
+void Node::setBestStationsNodeIdxVector(const std::vector<size_t> &bestStationsOrderedVector) {
+    _bestStationsNodeIdxVector = bestStationsOrderedVector;
+}
+
+void Node::clearPTLineSet() {
+    _ptLines.clear();
+}
+
+void Node::emplaceBackOutgoingEdge(size_t outgoingEdgeIndex) {
+    _outgoingEdgesIndex.emplace_back(outgoingEdgeIndex);
+}
+
+void Node::emplaceBackIncomingEdge(size_t incomingEdgeIndex) {
+    _incomingEdgesIndex.emplace_back(incomingEdgeIndex);
+}
diff --git a/src/instance/graph/Node.h b/src/instance/graph/Node.h
index 9e6ee2c393cf1b30672c58d69fb35b9417f39512..dbc5b7d05a0bc4a9bcedf8e215d0f26ebc2b1b85 100644
--- a/src/instance/graph/Node.h
+++ b/src/instance/graph/Node.h
@@ -10,6 +10,7 @@
 #include <set>
 #include <unordered_map>
 #include <vector>
+#include "../../algorithm/ShortestPath/Vehicle/ClosestDestinationsContainer.h"
 
 /**
  * Status used to qualify nodes as places with different goals (work, leisure, residential)
@@ -35,7 +36,8 @@ private:
     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
+    std::vector<size_t> _bestStationsNodeIdxVector;
+
 
 public:
     Node();
@@ -51,7 +53,7 @@ public:
     /**
      * @return True if this node contains one or more LineStop
      */
-    bool isPTNode();
+    bool isPTNode() const;
     /**
      * Adds a new LineStop to the LineStop set
      * @param line the line referenced by the LineStop
@@ -63,7 +65,7 @@ public:
      * @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) ;
+    static Status statusFromString(const 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
@@ -72,9 +74,9 @@ public:
     [[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;}
+    [[nodiscard]] const std::vector<int> & getIncomingEdges() const {return _incomingEdgesIndex;}
+    [[nodiscard]] const std::vector<int> & getOutgoingEdges() const {return _outgoingEdgesIndex;}
+    [[nodiscard]] const std::vector<LineStop> & getPTLinesSet() const {return _ptLines;}
 
     /**
      * Verify if _x, _y and _status are equal to check for node equality
@@ -88,6 +90,15 @@ public:
      * @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;
+
+    void emplaceBackClosestStation(size_t closestStationIdx);
+    void emplaceBackOutgoingEdge(size_t outgoingEdgeIndex);
+    void emplaceBackIncomingEdge(size_t incomingEdgeIndex);
+
+    [[nodiscard]] const std::vector<size_t>& getBestStationsNodeIdxVector() const;
+    void setBestStationsNodeIdxVector(const std::vector<size_t> &bestStationsOrderedVector);
+
+    void clearPTLineSet();
 };
 #include "Line.h"
 #include "LineStop.h"
diff --git a/src/instance/requests/Request.cpp b/src/instance/requests/Request.cpp
index a3625a3bc5818728bf48d8c5722f3874668be457..57ca2cc86465383c7e0393970b548bd22e263ce6 100644
--- a/src/instance/requests/Request.cpp
+++ b/src/instance/requests/Request.cpp
@@ -2,4 +2,281 @@
 // Created by rbernard on 22/01/24.
 //
 
+#include <charconv>
 #include "Request.h"
+#include "../../services/DatFile/DATRow.h"
+
+Request::Request(const size_t departureNodeIndex, const size_t arrivalNodeIndex, const TimeWindow &arrivalTw,
+                 const uint deltaTime, const uint weight) : _originNodeIndex(departureNodeIndex),
+                                                            _destinationNodeIndex(arrivalNodeIndex), _arrivalTW(arrivalTw),
+                                                            _deltaTime(deltaTime), _weight(weight) {
+    _departureTW = _arrivalTW - deltaTime;
+
+    if(_departureTW.min > _departureTW.max || _arrivalTW.min > _arrivalTW.max)
+        throw TimeWindow::invalid_time_window_exception();
+}
+
+Request::Request(const size_t departureNodeIndex, const size_t arrivalNodeIndex, const TimeWindow &arrivalTw,
+                 const uint deltaTime, const uint weight, const Graph& graph) :
+        _originNodeIndex(departureNodeIndex), _destinationNodeIndex(arrivalNodeIndex),
+        _arrivalTW(arrivalTw), _deltaTime(deltaTime), _weight(weight) {
+    _departureTW.min = _arrivalTW.min - deltaTime;
+    _departureTW.max = _arrivalTW.max - graph.getShortestSAEVPath(departureNodeIndex, arrivalNodeIndex);
+
+    if(_departureTW.min > _departureTW.max || _arrivalTW.min > _arrivalTW.max)
+        throw TimeWindow::invalid_time_window_exception();
+}
+
+Request::Request(const DATRow& currentRow, const Graph& graph) {
+    std::from_chars(currentRow[0].data(), currentRow[0].data() + currentRow[0].size(), _originNodeIndex);
+    std::from_chars(currentRow[1].data(), currentRow[1].data() + currentRow[1].size(), _destinationNodeIndex);
+
+    uint twMin, twMax;
+    bool setDepartureTW{false};
+    bool setArrivalTW{false};
+    if(currentRow.size() == 8) { //More flexible 8 arguments initialization where we can set either origin or destination TW or both
+        if (!currentRow[2].empty() && !currentRow[3].empty()) { //Departure TW
+            std::from_chars(currentRow[2].data(), currentRow[2].data() + currentRow[2].size(), twMin);
+            std::from_chars(currentRow[3].data(), currentRow[3].data() + currentRow[3].size(), twMax);
+            _departureTW = TimeWindow(twMin, twMax);
+            setDepartureTW = true;
+        }
+        if (!currentRow[4].empty() && !currentRow[5].empty()) { //Arrival TW
+            std::from_chars(currentRow[4].data(), currentRow[4].data() + currentRow[4].size(), twMin);
+            std::from_chars(currentRow[5].data(), currentRow[5].data() + currentRow[5].size(), twMax);
+            _arrivalTW = TimeWindow(twMin, twMax);
+            setArrivalTW = true;
+        }
+
+        std::from_chars(currentRow[6].data(), currentRow[6].data() + currentRow[6].size(), _deltaTime);
+        std::from_chars(currentRow[7].data(), currentRow[7].data() + currentRow[7].size(), _weight);
+    } else if (currentRow.size() == 6) { //Original 6 inputs request creation, setting a destination time window (needed for compatibility)
+        std::from_chars(currentRow[2].data(), currentRow[2].data() + currentRow[2].size(), twMin);
+        std::from_chars(currentRow[3].data(), currentRow[3].data() + currentRow[3].size(), twMax);
+        _arrivalTW = TimeWindow(twMin, twMax);
+        setArrivalTW = true;
+
+        std::from_chars(currentRow[4].data(), currentRow[4].data() + currentRow[4].size(), _deltaTime);
+        std::from_chars(currentRow[5].data(), currentRow[5].data() + currentRow[5].size(), _weight);
+    }
+
+    //If set arrival but not departure, deduce it from delta
+    if(!setDepartureTW && setArrivalTW) {
+        _departureTW.min = _arrivalTW.min - _deltaTime;
+        _departureTW.max = _arrivalTW.max - graph.getShortestSAEVPath(_originNodeIndex, _destinationNodeIndex);
+    //If set departure but not arrival, deduce it from delta
+    } else if(setDepartureTW && !setArrivalTW) {
+        _arrivalTW.min = _departureTW.min + graph.getShortestSAEVPath(_originNodeIndex, _destinationNodeIndex);
+        _arrivalTW.max = _departureTW.max + _deltaTime;
+    //If both TWs are set, override delta which might not be set
+    } else if(setDepartureTW && setArrivalTW) {
+        _deltaTime = _arrivalTW.max - _departureTW.max;
+    }
+
+    //Check TWs at the end
+    if(_departureTW.min > _departureTW.max || _arrivalTW.min > _arrivalTW.max)
+        throw TimeWindow::invalid_time_window_exception();
+}
+
+Request::Request(const DATRow& currentRow, double deltaRatio, const Graph& graph) : Request(currentRow, graph){
+    _deltaTime = floor(_deltaTime * deltaRatio);
+}
+
+std::vector<Request> Request::getRequestsFromFile(const std::string& datFilePath, const Graph& graph) {
+    std::vector<Request> requests;
+
+    std::ifstream infile(datFilePath);
+    assertm(!infile.fail(), "Failed to open the given file");
+    DATRow currentRow(',');
+
+    //-- Read params
+    infile >> currentRow;
+    std::cout << currentRow[0] << std::endl;
+    // Delta ratio
+    infile >> currentRow;
+    double deltaRatio;
+    std::from_chars(currentRow[0].data(), currentRow[0].data() + currentRow[0].length(), deltaRatio);
+    //-- End of params
+
+    //-- Read requests
+    infile >> currentRow; // Read and print comment line for format
+    std::cout << currentRow.toString() << std::endl;
+    while(infile >> currentRow && !currentRow[0].starts_with('#')) {
+        requests.emplace_back(currentRow, deltaRatio, graph);
+    }
+
+    return requests;
+}
+
+size_t Request::getOriginNodeIndex() const {
+    return _originNodeIndex;
+}
+
+size_t Request::getDestinationNodeIndex() const {
+    return _destinationNodeIndex;
+}
+
+const TimeWindow &Request::getArrivalTw() const {
+    return _arrivalTW;
+}
+
+uint Request::getDeltaTime() const {
+    return _deltaTime;
+}
+
+uint Request::getWeight() const {
+    return _weight;
+}
+
+const RequestRoute &Request::getCurrentRoute() const {
+    return _currentRoute;
+}
+
+const TimeWindow &Request::getDepartureTw() const {
+    return _departureTW;
+}
+
+uint Request::getMinDepartureTw() const {
+    return _departureTW.min;
+}
+
+
+uint Request::getMaxDepartureTw() const {
+    return _departureTW.max;
+}
+
+uint Request::getMinArrivalTw() const {
+    return _arrivalTW.min;
+}
+
+
+uint Request::getMaxArrivalTw() const {
+    return _arrivalTW.max;
+}
+
+//-----------------------------
+//---------- Setters ----------
+//-----------------------------
+void Request::setCurrentRoute(const RequestRoute &currentRoute) {
+    _currentRoute = currentRoute;
+}
+
+void Request::setDepartureTw(const TimeWindow &departureTw) {
+    _departureTW = departureTw;
+}
+
+//-----------------------------
+//--------- Route API ---------
+//-----------------------------
+
+int Request::getNodeIndex(int routeIndex) const {
+    return _currentRoute.getNodeIndex(routeIndex);
+}
+
+void Request::setNodeIndex(int routeIndex, int nodeIndex) {
+    _currentRoute.setNodeIndex(routeIndex, nodeIndex);
+}
+
+SAEVehicle const *Request::getSAEV(int routeIndex) const {
+    return _currentRoute.getSAEV(routeIndex);
+}
+
+void Request::setSAEV(int routeIndex, SAEVehicle *saev) {
+    _currentRoute.setSAEV(routeIndex, saev);
+}
+
+LineStop const *Request::getLineStop(int routeIndex) const {
+    return _currentRoute.getLineStop(routeIndex);
+}
+
+void Request::setLineStop(int routeIndex, LineStop *lineStop) {
+    _currentRoute.setLineStop(routeIndex, lineStop);
+}
+
+void Request::resetKeyPoint(int routeIndex) {
+    _currentRoute.resetKeyPoint(routeIndex);
+}
+
+std::string Request::to_string() const {
+    std::string res = "O=" + std::to_string(_originNodeIndex) + "," + "D=" + std::to_string(_destinationNodeIndex) //FIXME:replace with std::format whenever possible
+                      + ", " + _departureTW.to_string() + ", " + _arrivalTW.to_string()
+                      + ", w=" + std::to_string(_weight);
+    return res;
+}
+
+std::string Request::to_string_export() const {
+    std::string res = std::to_string(_originNodeIndex) + "," + std::to_string(_destinationNodeIndex) //FIXME:replace with std::format whenever possible
+            + "," + std::to_string(_arrivalTW.min) + "," + std::to_string(_arrivalTW.max)
+            + "," + std::to_string(_deltaTime) + "," + std::to_string(_weight);
+    return res;
+}
+
+Request::Request(const Graph &graph, const Request &baseRequest, const TransitAccess &transitEntry) {
+    _originNodeIndex = baseRequest.getOriginNodeIndex();
+    _destinationNodeIndex = transitEntry.getAccessNodeIdx();
+
+    _departureTW = baseRequest.getDepartureTw();
+
+    _arrivalTW.min = baseRequest.getDepartureTw().min + graph.getShortestSAEVPath(_originNodeIndex, transitEntry.getAccessNodeIdx());
+    _arrivalTW.max = transitEntry.getAccessTimestamp();
+
+    if(_departureTW.min > _departureTW.max || _arrivalTW.min > _arrivalTW.max)
+        throw TimeWindow::invalid_time_window_exception();
+
+    _transitTravelTimeRatio = baseRequest.getTransitTravelTimeRatio();
+    _deltaTime = UINT16_MAX;
+    _weight = baseRequest.getWeight();
+}
+
+Request::Request(const Graph &graph, const Request &baseRequest, const TransitAccess &transitExit,
+                 const SAEVKeyPoint &originSubRequestKeyPoint) {
+    _originNodeIndex = transitExit.getAccessNodeIdx();
+    _destinationNodeIndex = baseRequest.getDestinationNodeIndex();
+
+    _departureTW.min = transitExit.getAccessTimestamp();
+    _departureTW.max = baseRequest.getArrivalTw().max - graph.getShortestSAEVPath(transitExit.getAccessNodeIdx(), _destinationNodeIndex);
+
+    _arrivalTW.min = baseRequest.getArrivalTw().min;
+    if(originSubRequestKeyPoint.getNodeIndex() != originSubRequestKeyPoint.getCounterpart()->getNodeIndex())
+        _arrivalTW.max = originSubRequestKeyPoint.getMinTw() + baseRequest.getDeltaTime(); //Reduce max arrival TW to a value we are 100% sure is compatible with our current min departure time
+    else
+        _arrivalTW.max = std::min(baseRequest.getMinDepartureTw() + baseRequest.getDeltaTime(), baseRequest.getMaxArrivalTw()); //force earliest departure
+
+    if(_departureTW.min > _departureTW.max || _arrivalTW.min > _arrivalTW.max)
+        throw TimeWindow::invalid_time_window_exception();
+
+    _transitTravelTimeRatio = baseRequest.getTransitTravelTimeRatio();
+    _deltaTime = UINT16_MAX;
+    _weight = baseRequest.getWeight();
+}
+
+double Request::getTransitTravelTimeRatio() const {
+    return _transitTravelTimeRatio;
+}
+
+void Request::setTransitTravelTimeRatio(double transitTravelTimeRatio) {
+    _transitTravelTimeRatio = transitTravelTimeRatio;
+}
+
+double Request::computeTransitTravelTimeRatio(double deltaRatio, double travelTimeRatio) {
+    double computedRatio = (1.0 + (travelTimeRatio * (1.0 - deltaRatio)));
+    if(computedRatio < 1)
+        return 1;
+    else if(computedRatio > deltaRatio)
+        return deltaRatio;
+    else
+        return computedRatio;
+}
+
+double Request::computeTransitTravelTimeRatio(const Graph &graph, double travelTimeRatio) const {
+    double deltaRatio = (graph.getShortestSAEVPath(_originNodeIndex, _destinationNodeIndex))/(double)_deltaTime;
+    return computeTransitTravelTimeRatio(deltaRatio, travelTimeRatio);
+}
+
+bool Request::isMultimodal() const {
+    return _isMultimodal;
+}
+
+void Request::setIsMultimodal(bool isMultimodal) {
+    Request::_isMultimodal = isMultimodal;
+}
diff --git a/src/instance/requests/Request.h b/src/instance/requests/Request.h
index 775006c19e7a00b287e528f70936399c182715ba..cbfb917d0bd949635263bfd439188af352c785ac 100644
--- a/src/instance/requests/Request.h
+++ b/src/instance/requests/Request.h
@@ -6,8 +6,112 @@
 #define GREEDYALGORITHM_REQUEST_H
 
 
+#include "../../TimeWindow.h"
+#include "../../routes/requests/RequestRoute.h"
+#include "../graph/Graph.h"
+#include "../../algorithm/Multimodal/Heuristics/TransitAccess.h"
+#include "../../utils/Constants.h"
+#include "../../routes/vehicle/SAEVKeyPoint.h"
+
 class Request {
+private:
+    //Request base members (const and initialized on _request creation)
+    size_t _originNodeIndex; //Starting point of the user _request //TODO (?) change this to a Node pointer eventually
+    size_t _destinationNodeIndex; //
+    TimeWindow _departureTW;
+    TimeWindow _arrivalTW; //[min,max] time window for arrival to the destination node
+    uint _deltaTime; //Base delta time, aka the maximum total duration of the path to serve this _request
+    uint _weight; //How much space the requests takes in the vehicle (defaults to 1)
+    bool _isMultimodal{true}; //True iff the request accepts to be split by using the transit system
+
+    //Request helpful members (used for constraint propagation and remember the current state of the _request)
+    /**
+     * This object divides our request's route in four key points for each step in the user's path:<br>
+     * SAEV departure -> Transit entrance -> Transit exit -> SAEV end <br>
+     * Through this object, we'll save the request's path during resolution
+     */
+    RequestRoute _currentRoute{this};
+    /**
+     * A ratio by which to multiply the direct vehicle path to guesstimate an upper bound on transit travel time
+     */
+    double _transitTravelTimeRatio{Constants::BASE_TRANSIT_TRAVEL_TIME_RATIO};
+public:
+    Request() = default;
+    Request(const size_t departureNodeIndex, const size_t arrivalNodeIndex,
+            const TimeWindow &arrivalTw, const uint deltaTime, const uint weight);
+    Request(const size_t departureNodeIndex, const size_t arrivalNodeIndex, const TimeWindow &arrivalTw,
+            const uint deltaTime, const uint weight, const Graph& graph);
+    Request(const DATRow& currentRow, const Graph& graph);
+    Request(const DATRow& currentRow, double deltaRatio, const Graph& graph);
+    /**
+     * Entry sub request constructor, creating a new request from info on
+     * the base request and a transit entry candidate (entry node + max departure time)
+     * @param graph
+     * @param baseRequest
+     * @param transitEntry
+     */
+    Request(const Graph &graph, const Request &baseRequest, const TransitAccess &transitEntry);
+    /**
+     * Exit sub request constructor, creating a new request from info on
+     * the base request, a transit exit candidate (exit node + arrival time)
+     * and the current state of the entry sub request
+     * @param graph
+     * @param baseRequest
+     * @param transitExit
+     * @param originSubRequestKeyPoint
+     */
+    Request(const Graph &graph, const Request &baseRequest, const TransitAccess &transitExit,
+            const SAEVKeyPoint &originSubRequestKeyPoint);
+
+    static std::vector<Request> getRequestsFromFile(const std::string& datFilePath, const Graph& graph);
+
+    //Getters
+    [[nodiscard]] size_t getOriginNodeIndex() const;
+    [[nodiscard]] size_t getDestinationNodeIndex() const;
+    [[nodiscard]] const TimeWindow &getArrivalTw() const;
+    [[nodiscard]] uint getDeltaTime() const;
+    [[nodiscard]] uint getWeight() const;
+    [[nodiscard]] const RequestRoute &getCurrentRoute() const;
+    [[nodiscard]] const TimeWindow &getDepartureTw() const;
+    [[nodiscard]] uint getMinDepartureTw() const;
+    [[nodiscard]] uint getMaxDepartureTw() const;
+    [[nodiscard]] uint getMinArrivalTw() const;
+    [[nodiscard]] uint getMaxArrivalTw() const;
+
+    //Setters
+    void setCurrentRoute(const RequestRoute &currentRoute);
+    void setDepartureTw(const TimeWindow &departureTw);
+
+    //Route API delegation
+    [[nodiscard]] int getNodeIndex(int routeIndex) const;
+    void setNodeIndex(int routeIndex, int nodeIndex);
+
+    [[nodiscard]] const SAEVehicle *getSAEV(int routeIndex) const;
+    void setSAEV(int routeIndex, SAEVehicle *saev);
+
+    [[nodiscard]] const LineStop *getLineStop(int routeIndex) const;
+    void setLineStop(int routeIndex, LineStop *lineStop);
+
+    bool isMultimodal() const;
+    void setIsMultimodal(bool isMultimodal);
+
+    void resetKeyPoint(int routeIndex);
+
+    [[nodiscard]] double getTransitTravelTimeRatio() const;
+
+    void setTransitTravelTimeRatio(double transitTravelTimeRatio);
+
+    [[nodiscard]] static double computeTransitTravelTimeRatio(double deltaRatio, double travelTimeRatio);
+    [[nodiscard]] double computeTransitTravelTimeRatio(const Graph &graph, double travelTimeRatio) const;
 
+    /**
+     * Creates a string in an appropriate format for the request to be exported to a file that can be imported again
+     * <br> <br>
+     * Format : origin_idx,destination_idx,min,max,delta,capacity
+     * @return A properly formatted string to import back again
+     */
+    [[nodiscard]] std::string to_string_export() const;
+    [[nodiscard]] std::string to_string() const;
 };
 
 
diff --git a/src/routes/KeyPoint.cpp b/src/routes/KeyPoint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1239d71bb106431bac61781598110914d788ac27
--- /dev/null
+++ b/src/routes/KeyPoint.cpp
@@ -0,0 +1,16 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#include "KeyPoint.h"
+
+KeyPoint::KeyPoint() = default;
+KeyPoint::KeyPoint(int nodeIndex) : _nodeIndex(nodeIndex) {}
+
+int KeyPoint::getNodeIndex() const {
+    return _nodeIndex;
+}
+
+void KeyPoint::setNodeIndex(int nodeIndex) {
+    KeyPoint::_nodeIndex = nodeIndex;
+}
\ No newline at end of file
diff --git a/src/routes/KeyPoint.h b/src/routes/KeyPoint.h
new file mode 100644
index 0000000000000000000000000000000000000000..578eb10de35ce4c1609d554045f9d58f9a6d5dd6
--- /dev/null
+++ b/src/routes/KeyPoint.h
@@ -0,0 +1,31 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#ifndef GREEDYALGORITHM_KEYPOINT_H
+#define GREEDYALGORITHM_KEYPOINT_H
+
+
+#include <string>
+#include "../TimeWindow.h"
+
+class KeyPoint {
+private:
+    int _nodeIndex{-1};
+
+public:
+    KeyPoint();
+    explicit KeyPoint(int nodeIndex);
+
+    [[nodiscard]] int getNodeIndex() const;
+    void setNodeIndex(int nodeIndex);
+
+    [[nodiscard]] virtual bool check() const { return true; };
+
+    virtual ~KeyPoint() = default;
+
+    virtual std::string to_string() const { return std::to_string(_nodeIndex); }
+};
+
+
+#endif //GREEDYALGORITHM_KEYPOINT_H
diff --git a/src/routes/requests/RequestKeyPoint.cpp b/src/routes/requests/RequestKeyPoint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..40d52a5d92b19823be30a0c2ccf5324a46dee0b8
--- /dev/null
+++ b/src/routes/requests/RequestKeyPoint.cpp
@@ -0,0 +1,49 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#include "RequestKeyPoint.h"
+
+SAEVehicle const *RequestKeyPoint::getSAEV() const {
+    return _saev;
+}
+
+void RequestKeyPoint::setSAEV(const SAEVehicle *saev) {
+    _saev = saev;
+    _lineStop = nullptr;
+}
+
+LineStop const *RequestKeyPoint::getLineStop() const {
+    return _lineStop;
+}
+
+void RequestKeyPoint::setLineStop(const LineStop *lineStop) {
+    _lineStop = lineStop;
+    _saev = nullptr;
+}
+
+const TimeWindow &RequestKeyPoint::getArrivalTimeWindow() const {
+    return _arrivalTimeWindow;
+}
+
+void RequestKeyPoint::setArrivalTimeWindow(const TimeWindow &arrivalTimeWindow) {
+    _arrivalTimeWindow = arrivalTimeWindow;
+}
+
+const TimeWindow &RequestKeyPoint::getDepartureTimeWindow() const {
+    return _departureTimeWindow;
+}
+
+void RequestKeyPoint::setDepartureTimeWindow(const TimeWindow &departureTimeWindow) {
+    _departureTimeWindow = departureTimeWindow;
+}
+
+
+/**
+ * Verifies KeyPoint::check() + RequestKeyPoint specific constraints :
+ * @see KeyPoint::check()
+ * @return True if parent class checks are true and if one of _linestop xor _saev is a nullptr
+ */
+bool RequestKeyPoint::check() const {
+    return KeyPoint::check() && ((_saev != nullptr && _lineStop == nullptr) || (_saev == nullptr && _lineStop != nullptr));
+}
diff --git a/src/routes/requests/RequestKeyPoint.h b/src/routes/requests/RequestKeyPoint.h
new file mode 100644
index 0000000000000000000000000000000000000000..f96e2b918ec7017ab1b2753723ed017caa9327ec
--- /dev/null
+++ b/src/routes/requests/RequestKeyPoint.h
@@ -0,0 +1,44 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#ifndef GREEDYALGORITHM_REQUESTKEYPOINT_H
+#define GREEDYALGORITHM_REQUESTKEYPOINT_H
+
+
+#include "../KeyPoint.h"
+#include "../../instance/SAEVehicle.h"
+#include "../../instance/graph/LineStop.h"
+
+class RequestKeyPoint : public KeyPoint {
+private:
+    SAEVehicle const * _saev{};
+    LineStop const * _lineStop{};
+
+    //Constraint propagation variables
+    TimeWindow _arrivalTimeWindow{};
+    TimeWindow _departureTimeWindow{};
+
+public:
+    RequestKeyPoint() = default;
+    explicit RequestKeyPoint(SAEVehicle const* vehicle) : _saev(vehicle) {}
+    explicit RequestKeyPoint(LineStop const* lineStop) : _lineStop(lineStop) {}
+
+    [[nodiscard]] const SAEVehicle *getSAEV() const;
+    void setSAEV(const SAEVehicle *saev);
+
+    [[nodiscard]] const LineStop *getLineStop() const;
+    void setLineStop(const LineStop *lineStop);
+
+    [[nodiscard]] const TimeWindow &getArrivalTimeWindow() const;
+    void setArrivalTimeWindow(const TimeWindow &arrivalTimeWindow);
+
+    [[nodiscard]] const TimeWindow &getDepartureTimeWindow() const;
+    void setDepartureTimeWindow(const TimeWindow &departureTimeWindow);
+
+
+    [[nodiscard]] bool check() const override;
+};
+
+
+#endif //GREEDYALGORITHM_REQUESTKEYPOINT_H
diff --git a/src/routes/requests/RequestRoute.cpp b/src/routes/requests/RequestRoute.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f71f0f8764e6ae7852a86ece5ed82284083d6775
--- /dev/null
+++ b/src/routes/requests/RequestRoute.cpp
@@ -0,0 +1,51 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#include "RequestRoute.h"
+
+int RequestRoute::getNodeIndex(int routeIndex) const {
+    return _route[routeIndex].getNodeIndex();
+}
+
+void RequestRoute::setNodeIndex(int routeIndex, int nodeIndex) {
+    _route[routeIndex].setNodeIndex(nodeIndex);
+}
+
+SAEVehicle const *RequestRoute::getSAEV(int routeIndex) const {
+    return _route[routeIndex].getSAEV();
+}
+
+void RequestRoute::setSAEV(int routeIndex, SAEVehicle *saev) {
+    if(routeIndex == SAEV1 || routeIndex == SAEV2)
+        _route[routeIndex].setSAEV(saev);
+
+}
+
+LineStop const *RequestRoute::getLineStop(int routeIndex) const {
+    return _route[routeIndex].getLineStop();
+}
+
+void RequestRoute::setLineStop(int routeIndex, LineStop *lineStop) {
+    if(routeIndex == TRANSIT1 || routeIndex == TRANSIT2)
+        _route[routeIndex].setLineStop(lineStop);
+}
+
+void RequestRoute::resetKeyPoint(int routeIndex) {
+    RequestKeyPoint keyPoint = _route[routeIndex];
+    keyPoint.setNodeIndex(-1);
+    keyPoint.setSAEV(nullptr);
+    keyPoint.setLineStop(nullptr);
+}
+
+const RequestKeyPoint *RequestRoute::getRoute() const {
+    return _route;
+}
+
+Request *RequestRoute::getRequestPointer() const {
+    return _requestPointer;
+}
+
+void RequestRoute::setRequestPointer(Request *requestPointer) {
+    _requestPointer = requestPointer;
+}
diff --git a/src/routes/requests/RequestRoute.h b/src/routes/requests/RequestRoute.h
new file mode 100644
index 0000000000000000000000000000000000000000..d9f3bf19604fe0d2b755e14f9781dd0486e842b0
--- /dev/null
+++ b/src/routes/requests/RequestRoute.h
@@ -0,0 +1,46 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#ifndef GREEDYALGORITHM_REQUESTROUTE_H
+#define GREEDYALGORITHM_REQUESTROUTE_H
+
+
+#include "RequestKeyPoint.h"
+
+class Request;
+class RequestRoute {
+public:
+    enum RouteStructureEnum {
+        SAEV1 = 0,
+        TRANSIT1 = 1,
+        TRANSIT2 = 2,
+        SAEV2 = 3
+    };
+private:
+    RequestKeyPoint _route[4];
+    Request* _requestPointer{nullptr};
+public:
+    RequestRoute() = default;
+    explicit RequestRoute(Request* request) : _requestPointer(request) {};
+
+    [[nodiscard]] int getNodeIndex(int routeIndex) const;
+    void setNodeIndex(int routeIndex, int nodeIndex);
+
+    [[nodiscard]] const SAEVehicle *getSAEV(int routeIndex) const;
+    void setSAEV(int routeIndex, SAEVehicle *saev);
+
+    [[nodiscard]] const LineStop *getLineStop(int routeIndex) const;
+    void setLineStop(int routeIndex, LineStop *lineStop);
+
+    [[nodiscard]] const RequestKeyPoint *getRoute() const;
+
+    [[nodiscard]] Request *getRequestPointer() const;
+    void setRequestPointer(Request *requestPointer);
+
+    void resetKeyPoint(int routeIndex);
+};
+
+#include "../../instance/requests/Request.h"
+
+#endif //GREEDYALGORITHM_REQUESTROUTE_H
diff --git a/src/routes/vehicle/BestInsertionQueue.h b/src/routes/vehicle/BestInsertionQueue.h
new file mode 100644
index 0000000000000000000000000000000000000000..e28bb7114d87a47261a1f4e398a0e6912b5eb8a4
--- /dev/null
+++ b/src/routes/vehicle/BestInsertionQueue.h
@@ -0,0 +1,46 @@
+//
+// Created by romain on 18/06/24.
+//
+
+#ifndef GREEDYALGORITHM_BESTINSERTIONQUEUE_H
+#define GREEDYALGORITHM_BESTINSERTIONQUEUE_H
+
+
+#include <queue>
+#include "BestRequestInsertion.h"
+
+class BestInsertionQueue : public std::priority_queue<BestRequestInsertion, std::vector<BestRequestInsertion>, std::greater<>> {
+private:
+    SAEVKeyPoint &_originKP;
+    size_t _vehicleId{std::numeric_limits<size_t>::max()};
+
+public:
+    explicit BestInsertionQueue(SAEVKeyPoint &originKP) : _originKP(originKP) {}
+
+    BestInsertionQueue(SAEVKeyPoint &originKP, size_t vehicleId) : _originKP(originKP), _vehicleId(vehicleId) {}
+
+    BestInsertionQueue(SAEVKeyPoint &originKP, size_t vehicleId, size_t nbRequests) : BestInsertionQueue(originKP, vehicleId) {
+        c.reserve(nbRequests^2);
+    }
+
+    [[nodiscard]] size_t getVehicleId() const {
+        return _vehicleId;
+    }
+
+    [[nodiscard]] SAEVKeyPoint &getOriginKp() const {
+        return _originKP;
+    }
+
+    /**
+     * Returns and removes the top value from the priority queue
+     * @return The top value in the priority queue
+     */
+    BestRequestInsertion topAndPop() {
+        BestRequestInsertion top = this->top();
+        this->pop();
+        return top;
+    }
+};
+
+
+#endif //GREEDYALGORITHM_BESTINSERTIONQUEUE_H
diff --git a/src/routes/vehicle/BestRequestInsertion.h b/src/routes/vehicle/BestRequestInsertion.h
new file mode 100644
index 0000000000000000000000000000000000000000..02184d0d77657ffd5cbea4f9ef5836c40692f98e
--- /dev/null
+++ b/src/routes/vehicle/BestRequestInsertion.h
@@ -0,0 +1,71 @@
+//
+// Created by romain on 18/06/24.
+//
+
+#ifndef GREEDYALGORITHM_BESTREQUESTINSERTION_H
+#define GREEDYALGORITHM_BESTREQUESTINSERTION_H
+
+
+#include <cstddef>
+#include <compare>
+#include <cstdint>
+#include <limits>
+
+class BestRequestInsertion {
+private:
+    SAEVKeyPoint* _originInsertionKP;
+    SAEVKeyPoint* _destinationInsertionKP;
+    double _score{ std::numeric_limits<double>::max() };
+
+public:
+    /**
+     * Default constructor to allow pre-sizing containers
+     */
+    BestRequestInsertion() = default;
+    /**
+     * Constructs a BestRequestInsertion object, containing Origin/Destination insertion indexes and the score associated
+     * @param originInsertionKP Index of insertion for the origin of the request in the route
+     * @param destinationInsertionKP Index of insertion for the destination of the request in the route
+     * @param score
+     */
+    BestRequestInsertion(SAEVKeyPoint *originInsertionKP, SAEVKeyPoint *destinationInsertionKP, double score)
+            : _originInsertionKP(originInsertionKP), _destinationInsertionKP(destinationInsertionKP),
+              _score(score) {};
+
+    [[nodiscard]] SAEVKeyPoint *getOriginInsertionKp() {
+        return _originInsertionKP;
+    }
+
+    void setOriginInsertionKp(SAEVKeyPoint *originInsertionKp) {
+        _originInsertionKP = originInsertionKp;
+    }
+
+    [[nodiscard]] SAEVKeyPoint *getDestinationInsertionKp() {
+        return _destinationInsertionKP;
+    }
+
+    void setDestinationInsertionKp(SAEVKeyPoint *destinationInsertionKp) {
+        _destinationInsertionKP = destinationInsertionKp;
+    }
+
+    [[nodiscard]] double getScore() const {
+        return _score;
+    }
+
+    void setScore(double score) {
+        BestRequestInsertion::_score = score;
+    }
+
+    std::partial_ordering operator<=>(const BestRequestInsertion &rhs) const {
+        return _score <=> rhs._score;
+    }
+
+    std::string to_string() {
+        return "Insertion points (Origin:" + _originInsertionKP->to_string()
+        + "; Destination:" + _destinationInsertionKP->to_string()
+        + ") score = " + std::to_string(_score);
+    }
+};
+
+
+#endif //GREEDYALGORITHM_BESTREQUESTINSERTION_H
diff --git a/src/routes/vehicle/SAEVKeyPoint.cpp b/src/routes/vehicle/SAEVKeyPoint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..726bb6c987c2d80993dfadca7003d0d796f079fa
--- /dev/null
+++ b/src/routes/vehicle/SAEVKeyPoint.cpp
@@ -0,0 +1,141 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#include "SAEVKeyPoint.h"
+
+bool SAEVKeyPoint::check() const {
+    return KeyPoint::check();
+}
+
+SAEVKeyPoint *SAEVKeyPoint::getPredecessor() const {
+    return _predecessor;
+}
+
+void SAEVKeyPoint::setPredecessor(SAEVKeyPoint *predecessor) {
+    _predecessor = predecessor;
+}
+
+SAEVKeyPoint *SAEVKeyPoint::getSuccessor() const {
+    return _successor;
+}
+
+void SAEVKeyPoint::setSuccessor(SAEVKeyPoint *successor) {
+    _successor = successor;
+}
+
+uint SAEVKeyPoint::getCurrentOccupation() const {
+    return _currentOccupation;
+}
+
+void SAEVKeyPoint::setCurrentOccupation(uint currentCapacity) {
+    _currentOccupation = currentCapacity;
+}
+
+int SAEVKeyPoint::getMinTw() const {
+    return _minTW;
+}
+
+void SAEVKeyPoint::setMinTw(uint minTw) {
+    _minTW = minTw;
+}
+
+int SAEVKeyPoint::getMaxTw() const {
+    return _maxTW;
+}
+
+void SAEVKeyPoint::setMaxTw(uint maxTw) {
+    _maxTW = maxTw;
+}
+
+SAEVKeyPoint::SAEVKeyPoint(const Graph &graph, const Request &request, bool isOrigin) : _isOrigin(isOrigin), _requestPointer(&request) {
+    if(isOrigin) {
+        setNodeIndex(request.getOriginNodeIndex());
+        _minTW = request.getDepartureTw().min;
+        _maxTW = request.getDepartureTw().max;
+    } else {
+        setNodeIndex(request.getDestinationNodeIndex());
+        _minTW = request.getArrivalTw().min;
+        _maxTW = request.getArrivalTw().max;
+    }
+}
+
+bool SAEVKeyPoint::operator==(const SAEVKeyPoint &rhs) const {
+    return this == &rhs;
+}
+
+bool SAEVKeyPoint::isOrigin() const {
+    return _isOrigin;
+}
+
+bool SAEVKeyPoint::isDestination() const {
+    return !_isOrigin;
+}
+
+void SAEVKeyPoint::setIsOrigin(bool isOrigin) {
+    _isOrigin = isOrigin;
+}
+
+Request const *SAEVKeyPoint::getRequest() const {
+    return _requestPointer;
+}
+
+int SAEVKeyPoint::getDeltaTime() const {
+    return _isDepot ? 0 : _requestPointer->getDeltaTime();
+}
+
+void SAEVKeyPoint::setRequest(const Request *requestPointer) {
+    _requestPointer = requestPointer;
+    if(_isOrigin) {
+        setNodeIndex(_requestPointer->getOriginNodeIndex());
+        _minTW = _requestPointer->getDepartureTw().min;
+        _maxTW = _requestPointer->getDepartureTw().max;
+    } else {
+        setNodeIndex(_requestPointer->getDestinationNodeIndex());
+        _minTW = _requestPointer->getArrivalTw().min;
+        _maxTW = _requestPointer->getArrivalTw().max;
+    }
+}
+
+SAEVKeyPoint *SAEVKeyPoint::getCounterpart() const {
+    return _counterpart;
+}
+
+void SAEVKeyPoint::setCounterpart(SAEVKeyPoint *counterpart) {
+    _counterpart = counterpart;
+}
+
+SAEVKeyPoint::SAEVKeyPoint(size_t depotNodeIdx) : KeyPoint(depotNodeIdx) {
+    _isDepot = true;
+    setNodeIndex(depotNodeIdx);
+}
+
+bool SAEVKeyPoint::isDepot() const {
+    return _isDepot;
+}
+
+void SAEVKeyPoint::setIsDepot(bool isDepot) {
+    _isDepot = isDepot;
+}
+
+std::string SAEVKeyPoint::to_string() const {
+    std::string nodeType = "Depot, ";
+    if(!isDepot() && isOrigin()) { nodeType = "Origin, ";}
+    else if(!isDepot()) { nodeType = "Destination,"; }
+
+    return "(nodeIdx=" + std::to_string(getNodeIndex()) + ", Type=" + nodeType +
+    + "TW=[" + std::to_string(getMinTw()) + ";" + std::to_string(getMaxTw()) + "])";
+}
+
+void SAEVKeyPoint::resetBounds() {
+    if(isDepot()) {
+        _minTW = 0;
+        _maxTW = INT16_MAX;
+    } else if(isOrigin()) {
+        _minTW = _requestPointer->getMinDepartureTw();
+        _maxTW = _requestPointer->getMaxDepartureTw();
+    } else {
+        _minTW = _requestPointer->getMinArrivalTw();
+        _maxTW = _requestPointer->getMaxArrivalTw();
+    }
+}
diff --git a/src/routes/vehicle/SAEVKeyPoint.h b/src/routes/vehicle/SAEVKeyPoint.h
new file mode 100644
index 0000000000000000000000000000000000000000..f8bcaf75a2527d81fe081e65194ff2ac7b844e1b
--- /dev/null
+++ b/src/routes/vehicle/SAEVKeyPoint.h
@@ -0,0 +1,97 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#ifndef GREEDYALGORITHM_SAEVKEYPOINT_H
+#define GREEDYALGORITHM_SAEVKEYPOINT_H
+
+
+#include "../KeyPoint.h"
+#include "../../instance/graph/Graph.h"
+
+class Request;
+
+class SAEVehicle;
+
+class SAEVKeyPoint : public KeyPoint {
+private:
+    bool _isOrigin{};
+    bool _isDepot{false};
+    SAEVKeyPoint* _predecessor{nullptr};
+    SAEVKeyPoint* _successor{nullptr};
+    SAEVKeyPoint* _counterpart{nullptr};
+    int _currentOccupation{0};
+    uint _minTW{0};
+    uint _maxTW{INT16_MAX};
+    SAEVehicle const * _vehiclePointer{};
+    Request const * _requestPointer{};
+public:
+    /**
+     * Default KP constructor to allow pre-allocation of our route vector
+     */
+    SAEVKeyPoint() = default;
+    /**
+     * Depot KP initialization with [0;max] time windows and a depot node index
+     */
+    explicit SAEVKeyPoint(size_t depotNodeIdx);
+    /**
+     * SAEV KP initialization
+     * @param graph the instance's graph, used to get shortest path and estimate start time windows relative to distances
+     * @param request the request associated with our key point, required to initialize time windows
+     * @param isOrigin true iff the key point represents the request's origin, false otherwise
+     */
+    SAEVKeyPoint(const Graph &graph, const Request& request, bool isOrigin);
+
+    [[nodiscard]] SAEVKeyPoint *getPredecessor() const;
+    void setPredecessor(SAEVKeyPoint *predecessor);
+
+    [[nodiscard]] SAEVKeyPoint *getSuccessor() const;
+    void setSuccessor(SAEVKeyPoint *successor);
+
+    [[nodiscard]] uint getCurrentOccupation() const;
+    void setCurrentOccupation(uint currentCapacity);
+
+    [[nodiscard]] int getMinTw() const;
+    void setMinTw(uint minTw);
+
+    [[nodiscard]] int getMaxTw() const;
+    void setMaxTw(uint maxTw);
+
+    [[nodiscard]] bool isOrigin() const;
+    [[nodiscard]] bool isDestination() const;
+    void setIsOrigin(bool isOrigin);
+
+    [[nodiscard]] bool isDepot() const;
+    void setIsDepot(bool isDepot);
+
+    [[nodiscard]] const Request *getRequest() const;
+    void setRequest(const Request *requestPointer);
+
+    [[nodiscard]] SAEVKeyPoint *getCounterpart() const;
+    void setCounterpart(SAEVKeyPoint *counterpart);
+
+    [[nodiscard]] bool check() const override;
+
+    ~SAEVKeyPoint() override = default;
+
+    /**
+     * Two SAEVKeyPoints are equal iff they're the same instance (located at the same adress)
+     * It is done this way because a KP doesn't hold the info of the Request O/D it references. Only the route knows this
+     * @param rhs right hand side of the comparison
+     * @return True iff this and rhs are the same instance at the same adress
+     */
+    bool operator==(const SAEVKeyPoint &rhs) const;
+
+    [[nodiscard]] int getDeltaTime() const;
+
+    [[nodiscard]] std::string to_string() const override;
+
+    /**
+     * Resets the keypoint's min/max bounds to the linked request's original bounds
+     */
+    void resetBounds();
+};
+#include "../../instance/requests/Request.h"
+
+
+#endif //GREEDYALGORITHM_SAEVKEYPOINT_H
diff --git a/src/routes/vehicle/SAEVRoute.cpp b/src/routes/vehicle/SAEVRoute.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b1452bed2452cd1b1ee800c0e579ee2d83f162f5
--- /dev/null
+++ b/src/routes/vehicle/SAEVRoute.cpp
@@ -0,0 +1,574 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#include <queue>
+#include <iostream>
+#include "SAEVRoute.h"
+#include "BestInsertionQueue.h"
+
+#ifdef DEBUG_CONSTRAINT_PROPAGATION
+#include <iostream>
+#define DEBUG_PROPAG_MSG(str) do { std::cout << "[PROPAG] " << str << std::endl; } while( false )
+#else
+#define DEBUG_PROPAG_MSG(str) do { } while ( false )
+#endif
+
+SAEVRoute::SAEVRoute(const Graph &graph, const std::vector<Request> &requestList)
+        : SAEVRoute(graph, requestList, false) {}
+
+SAEVRoute::SAEVRoute(const Graph &graph, const std::vector<Request> &requestList, bool initMultimodal)
+        : _nbRequest(requestList.size()), _graph(&graph), _requestList(&requestList) {
+
+    if(!initMultimodal) {
+        _route.resize(_nbRequest*4); //nbRequest*2 O/D KeyPoints + nbRequest*2 Start/End depots (upper bound #vehicle = #requests
+    } else {
+        _route.resize(_nbRequest * 8); // 2*NbRequests O/D Keypoints, 2*NbRequests O/D vehicle depot Keypoints
+                                                // 2*NbRequests O/D Bus entry Keypoints, 2*NbRequests O/D Bus exit Keypoints
+    }
+
+    //Init Request O/D and Depot start/end key points
+    for(size_t i = 0; i < _nbRequest; ++i) {
+        //Create key O/D points
+        getRequestOrigin(i) = SAEVKeyPoint(graph, requestList[i], true); //origin
+        getRequestDestination(i) = SAEVKeyPoint(graph, requestList[i], false); //destination
+        //Link Origins and Destinations
+        getRequestOrigin(i).setCounterpart(&getRequestDestination(i));
+        getRequestDestination(i).setCounterpart(&getRequestOrigin(i));
+
+        //Create depot O/D KP (Upper Bound = nb requests)
+        getOriginDepot(i) = SAEVKeyPoint(graph.getDepotNodeIdx()); //start
+        getDestinationDepot(i) = SAEVKeyPoint(graph.getDepotNodeIdx()); //end
+        //Link Origin depots and Destination depots
+        getOriginDepot(i).setCounterpart(&getDestinationDepot(i));
+        getDestinationDepot(i).setCounterpart(&getOriginDepot(i));
+        getOriginDepot(i).setSuccessor(&getDestinationDepot(i));
+        getDestinationDepot(i).setPredecessor(&getOriginDepot(i));
+    }
+
+    //Set values and init all links for multimodal keypoints
+    if(initMultimodal) {
+        for (size_t i = 0; i < _nbRequest; ++i) {
+            //Set isOrigin accordingly
+            getEntrySubRequestOrigin(i).setIsOrigin(true);
+            getExitSubRequestOrigin(i).setIsOrigin(true);
+
+            //Link Origins and Destinations for entry/exit subrequests
+            getEntrySubRequestOrigin(i).setCounterpart(&getEntrySubRequestDestination(i));
+            getEntrySubRequestDestination(i).setCounterpart(&getEntrySubRequestOrigin(i));
+            getExitSubRequestOrigin(i).setCounterpart(&getExitSubRequestDestination(i));
+            getExitSubRequestDestination(i).setCounterpart(&getExitSubRequestOrigin(i));
+        }
+    }
+}
+
+void SAEVRoute::insertRequest(SAEVKeyPoint &originKp, SAEVKeyPoint * originRequestPredecessorKP, SAEVKeyPoint * destinationRequestPredecessorKP) {
+    SAEVKeyPoint* destinationKp = originKp.getCounterpart();
+
+    SAEVKeyPoint *originSuccKp = originRequestPredecessorKP->getSuccessor();
+    SAEVKeyPoint *destinationSuccKp = destinationRequestPredecessorKP->getSuccessor();
+
+    if(originRequestPredecessorKP != destinationRequestPredecessorKP) {
+        //Set values for O/D KPs
+        originKp.setPredecessor(originRequestPredecessorKP);
+        originKp.setSuccessor(originSuccKp);
+        destinationKp->setPredecessor(destinationRequestPredecessorKP);
+        destinationKp->setSuccessor(destinationSuccKp);
+
+        //Set values for predecessors/successors
+        originRequestPredecessorKP->setSuccessor(&originKp);
+        originSuccKp->setPredecessor(&originKp);
+        destinationRequestPredecessorKP->setSuccessor(destinationKp);
+        destinationSuccKp->setPredecessor(destinationKp);
+    } else {
+        //Set values for O/D KPs
+        originKp.setPredecessor(originRequestPredecessorKP);
+        originKp.setSuccessor(destinationKp);
+        destinationKp->setPredecessor(&originKp);
+        destinationKp->setSuccessor(destinationSuccKp);
+
+        //Set values for predecessors/successors
+        originRequestPredecessorKP->setSuccessor(&originKp);
+        originSuccKp->setPredecessor(destinationKp);
+    }
+    //Once insertion is done, update weights on the route
+    addRequestWeightToRoute(originKp);
+}
+
+void SAEVRoute::removeRequest(SAEVKeyPoint &originKp) {
+    assertm(originKp.getSuccessor() != nullptr, "Trying to remove a request that was already removed");
+    //Before undoing the insertion, update weights on the route
+    removeRequestWeightFromRoute(originKp);
+
+    SAEVKeyPoint* destinationKp = originKp.getCounterpart();
+
+    //get predecessor and successor for request
+    SAEVKeyPoint* originPredecessor = originKp.getPredecessor();
+    SAEVKeyPoint* originSuccessor = originKp.getSuccessor();
+    SAEVKeyPoint* destinationPredecessor = destinationKp->getPredecessor();
+    SAEVKeyPoint* destinationSuccessor = destinationKp->getSuccessor();
+
+    //Link pred and successor from origin and destination (cases differ if O/D are next to each other
+    if(originSuccessor == destinationKp) {
+        originPredecessor->setSuccessor(destinationSuccessor);
+        destinationSuccessor->setPredecessor(originPredecessor);
+    } else {
+        originPredecessor->setSuccessor(originSuccessor);
+        originSuccessor->setPredecessor(originPredecessor);
+        destinationPredecessor->setSuccessor(destinationSuccessor);
+        destinationSuccessor->setSuccessor(destinationPredecessor);
+    }
+
+    //Revert origin/destination key points to their default state
+    originKp.setPredecessor(nullptr);
+    originKp.setSuccessor(nullptr);
+    destinationKp->setPredecessor(nullptr);
+    destinationKp->setSuccessor(nullptr);
+    originKp.setMinTw(originKp.getRequest()->getMinDepartureTw());
+    originKp.setMaxTw(originKp.getRequest()->getMaxDepartureTw());
+    destinationKp->setMinTw(originKp.getRequest()->getMinArrivalTw());
+    destinationKp->setMaxTw(originKp.getRequest()->getMaxArrivalTw());
+}
+
+SAEVRouteChangelist
+SAEVRoute::tryAddRequest(size_t requestId, SAEVKeyPoint * originRequestPredecessorKP, SAEVKeyPoint * destinationRequestPredecessorKP) {
+    return tryAddRequest(getRequestOrigin(requestId), originRequestPredecessorKP, destinationRequestPredecessorKP);
+}
+
+SAEVRouteChangelist
+SAEVRoute::tryAddRequest(SAEVKeyPoint &requestOriginKeyPoint, SAEVKeyPoint * originRequestPredecessorKP, SAEVKeyPoint * destinationRequestPredecessorKP) {
+    const Request* request = requestOriginKeyPoint.getRequest();
+    SAEVKeyPoint const* destinationSuccessor = destinationRequestPredecessorKP->getSuccessor();
+
+    //Check if this request actually needs a vehicle
+    if(requestOriginKeyPoint.getNodeIndex() == requestOriginKeyPoint.getCounterpart()->getNodeIndex()) {
+        SAEVRouteChangelist res(this, &requestOriginKeyPoint);
+        res.setStatus(SAEVRouteChangelist::InsertionStatus::SUCCESS);
+        return res;
+    }
+
+    //Check vehicle capacity
+    SAEVKeyPoint const* currentKP = originRequestPredecessorKP;
+    do {
+        if(currentKP->getCurrentOccupation() + request->getWeight() > SAEVehicle::getCapacity()) {
+            DEBUG_PROPAG_MSG("WEIGHT VIOLATION : request weight = " + std::to_string(request->getWeight()) + " incompatible KP = " + currentKP->to_string());
+            return SAEVRouteChangelist(this, &requestOriginKeyPoint, originRequestPredecessorKP, destinationRequestPredecessorKP, SAEVRouteChangelist::InsertionStatus::FAILURE_PRECONDITION_WEIGHT);
+        }
+        currentKP = currentKP->getSuccessor();
+    } while (currentKP != destinationSuccessor && currentKP != nullptr);
+
+    //Do basic checks on neighbouring nodes from our Origin/Destination insertion points
+    bool isValid = doNeighbouringTWChecks(requestOriginKeyPoint, originRequestPredecessorKP, destinationRequestPredecessorKP);
+
+    if(isValid) {
+        return insertRequestWithPropagation(requestOriginKeyPoint, originRequestPredecessorKP, destinationRequestPredecessorKP);
+    } else {
+        DEBUG_PROPAG_MSG("TW VIOLATION on neighbour KPs");
+        return SAEVRouteChangelist(this, &requestOriginKeyPoint, originRequestPredecessorKP, destinationRequestPredecessorKP, SAEVRouteChangelist::InsertionStatus::FAILURE_PRECONDITION_TW);
+    }
+}
+
+bool
+SAEVRoute::doNeighbouringTWChecks(const SAEVKeyPoint &originKP, const SAEVKeyPoint *originPredecessor, const SAEVKeyPoint *destinationPredecessor) const {
+
+    const SAEVKeyPoint* destinationKP = originKP.getCounterpart();
+    const SAEVKeyPoint* originSuccessor = originPredecessor->getSuccessor();
+    const size_t originNodeIndex = originKP.getNodeIndex();
+    const size_t destinationNodeIndex = destinationKP->getNodeIndex();
+
+    if(originPredecessor != destinationPredecessor)
+    {
+        SAEVKeyPoint const* destinationSuccessor = destinationPredecessor->getSuccessor();
+
+        //Tests time windows Origin (yes this if-else could be one giant OR, but I'd rather separate every case)
+        uint predOriginTimeWindow = originPredecessor->getMinTw() + _graph->getShortestSAEVPath(originPredecessor->getNodeIndex(), originNodeIndex);
+        uint predDestinationTimeWindow = destinationPredecessor->getMinTw() + _graph->getShortestSAEVPath(destinationPredecessor->getNodeIndex(), destinationNodeIndex);
+        if(predOriginTimeWindow > originKP.getMaxTw())
+            return false;
+        else if(originKP.getMinTw() + _graph->getShortestSAEVPath(originKP.getNodeIndex(), originSuccessor->getNodeIndex()) > originSuccessor->getMaxTw()) // Could be removed ?
+            return false;
+        else if(predOriginTimeWindow + _graph->getShortestSAEVPath(originKP.getNodeIndex(), originSuccessor->getNodeIndex()) > originSuccessor->getMaxTw())
+            return false;
+        //Tests time windows Destination
+        else if(predDestinationTimeWindow > destinationKP->getMaxTw())
+            return false;
+        else if(destinationKP->getMinTw() + _graph->getShortestSAEVPath(destinationKP->getNodeIndex(), destinationSuccessor->getNodeIndex()) > destinationSuccessor->getMaxTw()) //could be removed ?
+            return false;
+        else if(predOriginTimeWindow + _graph->getShortestSAEVPath(destinationKP->getNodeIndex(), destinationSuccessor->getNodeIndex()) > destinationSuccessor->getMaxTw())
+            return false;
+    } else { //We need a specific case if origin and destination are inserted after the same node
+        uint predMinTWToOrigin = originPredecessor->getMinTw() + _graph->getShortestSAEVPath(originPredecessor->getNodeIndex(), originNodeIndex);
+        uint predMinTWToDest = predMinTWToOrigin + _graph->getShortestSAEVPath(originNodeIndex, destinationNodeIndex);
+        if(predMinTWToOrigin > originKP.getMaxTw()) //Path from pred to O
+            return false;
+        else if(predMinTWToDest > destinationKP->getMaxTw()) //Path from pred to D
+            return false;
+        else if(predMinTWToDest + _graph->getShortestSAEVPath(destinationNodeIndex, originSuccessor->getNodeIndex()) > originSuccessor->getMaxTw()) //Path from pred to successor
+            return false;
+        else if(destinationKP->getMinTw() + _graph->getShortestSAEVPath(destinationNodeIndex, originSuccessor->getNodeIndex()) > originSuccessor->getMaxTw()) //Path from D to successor
+            return false;
+        else if(originKP.getMinTw() + _graph->getShortestSAEVPath(originNodeIndex, destinationNodeIndex) //Path from O to successor
+        + _graph->getShortestSAEVPath(destinationNodeIndex, originSuccessor->getNodeIndex()) > originSuccessor->getMaxTw())
+            return false;
+    }
+
+    return true;
+}
+
+SAEVRouteChangelist SAEVRoute::insertRequestWithPropagation(SAEVKeyPoint &originKP, SAEVKeyPoint * originRequestPredecessorKP,
+                                                            SAEVKeyPoint * destinationRequestPredecessorKP) {
+    //Init changelist and detour score
+    SAEVRouteChangelist changelist{this, &originKP, originRequestPredecessorKP, destinationRequestPredecessorKP,
+                                   SAEVRouteChangelist::InsertionStatus::FAILURE_MIN};
+    double detourScore = getDetourScore(originKP, originRequestPredecessorKP, destinationRequestPredecessorKP);
+    //Properly insert the request to facilitate constraint propagation
+    insertRequest(originKP, originRequestPredecessorKP, destinationRequestPredecessorKP);
+
+    //Initialize bound propagation signal queue (each item signals a modification done on one of a KeyPoint
+    std::queue<std::pair<int, SAEVKeyPoint *>> boundPropagationQueue{};
+    SAEVKeyPoint *destinationKP = originKP.getCounterpart();
+    boundPropagationQueue.emplace(Min, originKP.getPredecessor());
+    boundPropagationQueue.emplace(Max, originKP.getSuccessor());
+    boundPropagationQueue.emplace(Min, destinationKP->getPredecessor());
+    boundPropagationQueue.emplace(Max, destinationKP->getSuccessor());
+    boundPropagationQueue.emplace(Min, &originKP);
+    boundPropagationQueue.emplace(Max, &originKP);
+    boundPropagationQueue.emplace(Min, destinationKP);
+    boundPropagationQueue.emplace(Max, destinationKP);
+
+    doBoundPropagation(boundPropagationQueue, changelist);
+    changelist.setScore(detourScore);
+    return changelist;
+}
+
+
+SAEVRouteChangelist SAEVRoute::doBoundPropagation(std::queue<std::pair<int, SAEVKeyPoint *>> &boundPropagationQueue,
+                                                  SAEVRouteChangelist &changelist) {
+    //Pre-init variables used in the loop
+    int oldValue;
+    int newValue;
+    SAEVKeyPoint * predecessorKP;
+    SAEVKeyPoint * successorKP;
+    SAEVKeyPoint * counterpartKP; //An Origin's Destination, or a Destination's Origin
+
+
+    while(!boundPropagationQueue.empty()) {
+        auto const& [bound, keyPoint] = boundPropagationQueue.front();
+        boundPropagationQueue.pop();
+        counterpartKP = keyPoint->getCounterpart();
+        if(bound == Min) {
+            successorKP = keyPoint->getSuccessor();
+            if(successorKP != nullptr) {
+                //Check neighbouring time window
+                oldValue = successorKP->getMinTw();
+                newValue = keyPoint->getMinTw() + _graph->getShortestSAEVPath(keyPoint->getNodeIndex(), successorKP->getNodeIndex());
+                if (oldValue < newValue) {
+                    if (newValue > successorKP->getMaxTw()) {
+                        DEBUG_PROPAG_MSG("\tMIN TW VIOLATION");
+                        changelist.setStatus(SAEVRouteChangelist::InsertionStatus::FAILURE_MIN);
+                        return changelist;
+                    }
+                    changelist.emplace_back(*successorKP, Min, newValue - oldValue);
+                    successorKP->setMinTw(newValue);
+                    boundPropagationQueue.emplace(Min, successorKP);
+                }
+            }
+            //Check counterpart key point delta time
+            oldValue = counterpartKP->getMinTw();
+            newValue = keyPoint->getMinTw() - keyPoint->getDeltaTime();
+            if(!keyPoint->isDepot() && keyPoint->isDestination() && oldValue < newValue) {
+                if (newValue > counterpartKP->getMaxTw()) {
+                    DEBUG_PROPAG_MSG("\tMIN DELTA Destination->Origine");
+                    changelist.setStatus(SAEVRouteChangelist::InsertionStatus::FAILURE_DELTA_MIN);
+                    return changelist;
+                }
+                changelist.emplace_back(*counterpartKP, Min, newValue - oldValue);
+                counterpartKP->setMinTw(newValue);
+                boundPropagationQueue.emplace(Min, counterpartKP);
+            }
+        } else { //MAX
+            predecessorKP = keyPoint->getPredecessor();
+            if(predecessorKP != nullptr) {
+                //Check neighbouring time window
+                oldValue = predecessorKP->getMaxTw();
+                newValue = keyPoint->getMaxTw() - _graph->getShortestSAEVPath(predecessorKP->getNodeIndex(), keyPoint->getNodeIndex());
+                if(oldValue > newValue) {
+                    if (predecessorKP->getMinTw() > newValue) {
+                        DEBUG_PROPAG_MSG("\tMAX TW VIOLATION");
+                        changelist.setStatus(SAEVRouteChangelist::InsertionStatus::FAILURE_MAX);
+                        return changelist;
+                    }
+                    changelist.emplace_back(*predecessorKP, Max, newValue - oldValue);
+                    predecessorKP->setMaxTw(newValue);
+                    boundPropagationQueue.emplace(Max, predecessorKP);
+                }
+            }
+            //Check counterpart key point delta time
+            oldValue = counterpartKP->getMaxTw();
+            newValue = keyPoint->getMaxTw() + keyPoint->getDeltaTime();
+            if(!keyPoint->isDepot() && keyPoint->isOrigin() && oldValue > newValue) {
+                if (counterpartKP->getMinTw() > newValue) {
+                    DEBUG_PROPAG_MSG("\tMAX DELTA Origine->Destination");
+                    changelist.setStatus(SAEVRouteChangelist::InsertionStatus::FAILURE_DELTA_MAX);
+                    return changelist;
+                }
+                changelist.emplace_back(*counterpartKP, Max, oldValue - newValue);
+                counterpartKP->setMaxTw(newValue);
+                boundPropagationQueue.emplace(Max, counterpartKP);
+            }
+        }
+    }
+
+    changelist.setStatus(SAEVRouteChangelist::InsertionStatus::SUCCESS);
+    return changelist;
+}
+
+double SAEVRoute::getDetourScore(const SAEVKeyPoint &originKP, const SAEVKeyPoint * originRequestPredecessorKP,
+                                 const SAEVKeyPoint * destinationRequestPredecessorKP) const {
+    double score;
+    const SAEVKeyPoint* destinationKP = originKP.getCounterpart();
+    const SAEVKeyPoint* originSuccKP = originRequestPredecessorKP->getSuccessor();
+    const SAEVKeyPoint* destinationSuccKP = destinationRequestPredecessorKP->getSuccessor();
+
+    if(originRequestPredecessorKP != destinationRequestPredecessorKP) {
+        const SAEVKeyPoint* destinationPredKP = destinationRequestPredecessorKP;
+
+        //Origin Detour
+        score = _graph->getShortestSAEVPath(originRequestPredecessorKP->getNodeIndex(), originKP.getNodeIndex()) //T(Pred(O), D)
+                + _graph->getShortestSAEVPath(originKP.getNodeIndex(), originSuccKP->getNodeIndex()) //T(O, Succ(D))
+                - _graph->getShortestSAEVPath(originRequestPredecessorKP->getNodeIndex(), originSuccKP->getNodeIndex()); //T(Pred(O), Succ(O))
+
+        //Destination Detour
+        score += _graph->getShortestSAEVPath(destinationPredKP->getNodeIndex(), destinationKP->getNodeIndex()) //T(Pred(D), D))
+                + _graph->getShortestSAEVPath(destinationKP->getNodeIndex(), destinationSuccKP->getNodeIndex()) //T(D, Succ(D)))
+                - _graph->getShortestSAEVPath(destinationPredKP->getNodeIndex(), destinationSuccKP->getNodeIndex()); //T(Pred(D), Succ(D)))
+    } else {
+        score = _graph->getShortestSAEVPath(originRequestPredecessorKP->getNodeIndex(), originKP.getNodeIndex()) //T(Pred(O), O)
+                + _graph->getShortestSAEVPath(originKP.getNodeIndex(), destinationKP->getNodeIndex()) //T(O, D)
+                + _graph->getShortestSAEVPath(destinationKP->getNodeIndex(), destinationSuccKP->getNodeIndex()) //T(D, Succ(D))
+                - _graph->getShortestSAEVPath(originRequestPredecessorKP->getNodeIndex(), destinationSuccKP->getNodeIndex()); //T(Pred(O), Succ(D))
+    }
+    return score;
+}
+
+std::string SAEVRoute::to_string(size_t vehicleId) {
+    std::string routeString;
+    SAEVKeyPoint const* currentKeyPoint = &getOriginDepot(vehicleId);
+    while(currentKeyPoint != nullptr) {
+        routeString += currentKeyPoint->to_string() + " --> ";
+        currentKeyPoint = currentKeyPoint->getSuccessor();
+    }
+    routeString.erase(routeString.length() - 5, routeString.length());
+
+    return routeString;
+}
+
+void SAEVRoute::exportToFile() {
+    //TODO
+}
+
+bool SAEVRoute::checkRouteTimeWindows(size_t vehicleId) {
+    SAEVKeyPoint const* currentKeyPoint = &getOriginDepot(vehicleId);
+    SAEVKeyPoint const* succKP;
+    SAEVKeyPoint const* counterpartKP;
+    while(currentKeyPoint != nullptr) {
+        succKP = currentKeyPoint->getSuccessor();
+        counterpartKP = currentKeyPoint->getCounterpart();
+        //MIN/MAX
+        if(succKP != nullptr) {
+            if(!currentKeyPoint->isDepot() && currentKeyPoint->getMinTw() + _graph->getShortestSAEVPath(currentKeyPoint->getNodeIndex(), succKP->getNodeIndex()) > succKP->getMaxTw() ) {
+                DEBUG_PROPAG_MSG("MIN TW VIOLATION : " + currentKeyPoint->to_string() + " > " + succKP->to_string());
+                return false;
+            }
+            if(!succKP->isDepot() && currentKeyPoint->getMaxTw() + _graph->getShortestSAEVPath(currentKeyPoint->getNodeIndex(), succKP->getNodeIndex()) < succKP->getMaxTw() ) {
+                DEBUG_PROPAG_MSG("MAX TW VIOLATION : " + currentKeyPoint->to_string() + " < " + succKP->to_string());
+                return false;
+            }
+        }
+        //DELTA
+        if((!currentKeyPoint->isDepot() && currentKeyPoint->isOrigin() && counterpartKP != nullptr)
+        && (counterpartKP->getMinTw() - currentKeyPoint->getMinTw() > currentKeyPoint->getRequest()->getDeltaTime()
+        ||  counterpartKP->getMaxTw() - currentKeyPoint->getMaxTw() > currentKeyPoint->getRequest()->getDeltaTime())) {
+            DEBUG_PROPAG_MSG("DELTA VIOLATION : " + currentKeyPoint->to_string() + " " + counterpartKP->to_string());
+            return false;
+        }
+        currentKeyPoint = currentKeyPoint->getSuccessor();
+    }
+
+    return true;
+}
+
+/** TODO: move to BestInsertionQueue class with a route parameter ?
+ * Initializes a BestInsertionQueue to guide the best insertion heuristic
+ * @param requestOriginKeyPoint Reference to the origin key point
+ * @param vehicleId The ID of the vehicle in which we look for new best insertions
+ * @return The created BestInsertionQueue filled with the best insertions for the given request and vehicle IDs
+ */
+BestInsertionQueue SAEVRoute::getBestInsertionsQueue(size_t requestId, size_t vehicleId) {
+    return getBestInsertionsQueue(getRequestOrigin(requestId), vehicleId);
+}
+
+/** TODO: move to BestInsertionQueue class with a route parameter ?
+ * Initializes a BestInsertionQueue to guide the best insertion heuristic
+ * @param requestOriginKeyPoint Reference to the origin key point
+ * @param vehicleId The ID of the vehicle in which we look for new best insertions
+ * @return The created BestInsertionQueue filled with the best insertions for the given request and vehicle IDs
+ */
+BestInsertionQueue SAEVRoute::getBestInsertionsQueue(SAEVKeyPoint &originKP, size_t vehicleId) {
+    BestInsertionQueue bestInsertionQueue(originKP, vehicleId, _nbRequest ^ 2);
+
+    //Init variables used during iteration
+    double score;
+    SAEVKeyPoint * originInsertionKeyPoint = &getOriginDepot(vehicleId);
+    SAEVKeyPoint * destinationInsertionKeyPoint = originInsertionKeyPoint;
+
+    //iterate over possible origin/destination pairs for the given vehicle
+    while(originInsertionKeyPoint->getSuccessor() != nullptr) {
+        while(destinationInsertionKeyPoint->getSuccessor() != nullptr) {
+            score = getDetourScore(originKP, originInsertionKeyPoint, destinationInsertionKeyPoint);
+            bestInsertionQueue.emplace(originInsertionKeyPoint, destinationInsertionKeyPoint, score);
+            destinationInsertionKeyPoint = destinationInsertionKeyPoint->getSuccessor();
+        }
+
+        //Iterate over possible origins and reset destination to being the same point as the origin
+        originInsertionKeyPoint = originInsertionKeyPoint->getSuccessor();
+        destinationInsertionKeyPoint = originInsertionKeyPoint;
+    }
+
+    return bestInsertionQueue;
+}
+
+/**
+ * Initializes a BestInsertionQueue to guide the best insertion heuristic while checking neighbouring TW checks
+ * @param requestId The ID of the base request for which we search best insertions
+ * @param vehicleId The ID of the vehicle in which we look for new best insertions
+ * @return The created BestInsertionQueue filled with the best insertions for the given request and vehicle IDs
+ */
+BestInsertionQueue SAEVRoute::getBestFeasibleInsertionsQueue(size_t requestId, size_t vehicleId) {
+    return getBestFeasibleInsertionsQueue(getRequestOrigin(requestId), vehicleId);
+}
+
+/**
+ * Initializes a BestInsertionQueue to guide the best insertion heuristic while checking neighbouring TW checks
+ * @param requestOriginKeyPoint Reference to the origin key point of the request for which we search best insertions
+ * @param vehicleId The ID of the vehicle in which we look for new best insertions
+ * @return The created BestInsertionQueue filled with the best insertions for the given request and vehicle IDs
+ */
+BestInsertionQueue SAEVRoute::getBestFeasibleInsertionsQueue(SAEVKeyPoint &originKP, size_t vehicleId) {
+    BestInsertionQueue bestInsertionQueue(originKP, vehicleId, _nbRequest ^ 2);
+    getBestFeasibleInsertionsQueue(bestInsertionQueue, originKP, vehicleId);
+    return bestInsertionQueue;
+}
+
+/**
+ * Search for new best insertions to add into the given best insertion queue for a given request and vehicle
+ * @param bestInsertionQueue The queue we wish to add the new feasible best insertions to
+ * @param requestOriginKeyPoint Reference to the origin key point of the request for which we search best insertions
+ * @param vehicleId The ID of the vehicle in which we look for new best insertions
+ */
+void SAEVRoute::getBestFeasibleInsertionsQueue(BestInsertionQueue& bestInsertionQueue, const SAEVKeyPoint &requestOriginKeyPoint, size_t vehicleId) {
+    //Init variables used during iteration
+    double score;
+    SAEVKeyPoint * originInsertionKeyPoint = &getOriginDepot(vehicleId);
+    SAEVKeyPoint * destinationInsertionKeyPoint = originInsertionKeyPoint;
+
+    //iterate over possible origin/destination pairs for the given vehicle
+    while(originInsertionKeyPoint->getSuccessor() != nullptr) {
+        while(destinationInsertionKeyPoint->getSuccessor() != nullptr) {
+            if(doNeighbouringTWChecks(requestOriginKeyPoint, originInsertionKeyPoint, destinationInsertionKeyPoint)) {
+                score = getDetourScore(requestOriginKeyPoint, originInsertionKeyPoint, destinationInsertionKeyPoint);
+                bestInsertionQueue.emplace(originInsertionKeyPoint, destinationInsertionKeyPoint, score);
+            }
+            destinationInsertionKeyPoint = destinationInsertionKeyPoint->getSuccessor();
+        }
+
+        //Iterate over possible origins and reset destination to being the same point as the origin
+        originInsertionKeyPoint = originInsertionKeyPoint->getSuccessor();
+        destinationInsertionKeyPoint = originInsertionKeyPoint;
+    }
+}
+
+void SAEVRoute::addRequestWeightToRoute(SAEVKeyPoint &requestOriginKeyPoint) {
+    SAEVKeyPoint* currentKP = &requestOriginKeyPoint;
+    uint requestWeight = currentKP->getRequest()->getWeight();
+    currentKP->setCurrentOccupation(currentKP->getPredecessor()->getCurrentOccupation() + requestWeight); //O.Weight = Prec(O).Weight + R.Weight (request enters the vehicle=)
+    do {
+        currentKP = currentKP->getSuccessor();
+        currentKP->setCurrentOccupation(currentKP->getCurrentOccupation() + requestWeight);
+    } while (currentKP != requestOriginKeyPoint.getCounterpart());
+    currentKP->setCurrentOccupation(currentKP->getPredecessor()->getCurrentOccupation() - requestWeight); //D.Weight = Prec(D).Weight - R.Weight (request leaves the vehicle)
+}
+
+void SAEVRoute::removeRequestWeightFromRoute(SAEVKeyPoint& requestOriginKeyPoint) {
+    SAEVKeyPoint* currentKP = &requestOriginKeyPoint;
+    uint requestWeight = currentKP->getRequest()->getWeight();
+    currentKP->setCurrentOccupation(0); //reset request weight on origin KP
+    do {
+        currentKP = currentKP->getSuccessor();
+        currentKP->setCurrentOccupation(currentKP->getCurrentOccupation() - requestWeight);
+    } while (currentKP != requestOriginKeyPoint.getCounterpart());
+    currentKP->setCurrentOccupation(0); //reset request weight on destination KP
+}
+
+void SAEVRoute::insertRequestInNewVehicle(SAEVKeyPoint &originKp) {
+    size_t vehicleId = addNewActiveVehicle();
+    DEBUG_PROPAG_MSG("NEW VEHICLE CREATED, ID :" + std::to_string(vehicleId));
+
+    tryAddRequest(originKp, &getOriginDepot(vehicleId), &getOriginDepot(vehicleId));
+}
+
+std::string SAEVRoute::to_string() {
+    std::string res;
+    for(size_t i = 0; i <= _lastActiveVehicleId; ++i){
+        res += "Vehicle #" + std::to_string(i) + ": " + to_string(i) + "\n";
+    }
+    return res;
+}
+
+SAEVRouteChangelist SAEVRoute::removeRequestWithPropagation(SAEVKeyPoint &originKP) {
+    //save ref to part of the route we have to update before removing
+    //FIXME : best would be to link to depot/vehicle to directly start from the origin depot instead of having two whiles
+    SAEVKeyPoint* originPredKp = originKP.getPredecessor();
+    assertm(originPredKp != nullptr, "Trying to remove a key point that's not in any route");
+    //Actually remove request
+    removeRequest(originKP);
+
+    std::queue<std::pair<int, SAEVKeyPoint*>> propagationQueue;
+    //Now iterate over predecessor KPs to reset their bounds and add to propagation queue
+    SAEVKeyPoint* currentKP = originPredKp;
+    while (currentKP != nullptr) {
+        currentKP->resetBounds();
+        propagationQueue.emplace(Min, currentKP);
+        propagationQueue.emplace(Max, currentKP);
+        currentKP = currentKP->getPredecessor();
+    }
+    //Do the same over successor KPs to be fully done with the
+    currentKP = originPredKp->getSuccessor();
+    while (currentKP != nullptr) {
+        currentKP->resetBounds();
+        propagationQueue.emplace(Min, currentKP);
+        propagationQueue.emplace(Max, currentKP);
+        currentKP = currentKP->getSuccessor();
+    }
+
+    SAEVRouteChangelist changelist{this, nullptr};
+    doBoundPropagation(propagationQueue, changelist);
+    return changelist;
+}
+
+SAEVRouteChangelist SAEVRoute::removeRequestWithPropagation(size_t requestId, bool isEntry) {
+    SAEVKeyPoint& originKeyPoint = isEntry ? getEntrySubRequestOrigin(requestId) : getExitSubRequestOrigin(requestId);
+    return removeRequestWithPropagation(originKeyPoint);
+}
+
+SAEVRouteChangelist SAEVRoute::removeRequestWithPropagation(size_t requestId) {
+    return removeRequestWithPropagation(getRequestOrigin(requestId));
+}
+
+SAEVKeyPoint &SAEVRoute::getSubRequestOrigin(size_t baseRequestId, bool isEntry) {
+    if(isEntry)
+        return getEntrySubRequestOrigin(baseRequestId);
+    else
+        return getExitSubRequestOrigin(baseRequestId);
+}
+
diff --git a/src/routes/vehicle/SAEVRoute.h b/src/routes/vehicle/SAEVRoute.h
new file mode 100644
index 0000000000000000000000000000000000000000..78ddd58b35a28ede3ecfbe0316404d5246bd98d4
--- /dev/null
+++ b/src/routes/vehicle/SAEVRoute.h
@@ -0,0 +1,240 @@
+//
+// Created by romain on 22/03/24.
+//
+
+#ifndef GREEDYALGORITHM_SAEVROUTE_H
+#define GREEDYALGORITHM_SAEVROUTE_H
+
+
+#include <vector>
+#include "SAEVKeyPoint.h"
+#include "BestRequestInsertion.h"
+#include "BestInsertionQueue.h"
+
+class SAEVRouteChangelist;
+class SAEVRoute {
+private:
+    std::vector<SAEVKeyPoint> _route;
+    size_t _nbRequest;
+    const Graph* _graph;
+    const std::vector<Request>* _requestList;
+    size_t _lastActiveVehicleId{0};
+public:
+    SAEVRoute() = default;
+    /**
+     * Initializes a route following a tabular doubly chained approach where 0->nbRequest*2-1 are Request Origin (even)/Destination (odd)
+     * and nbRequest*2 -> nbRequest*4-1 are vehicle start (even)/end (odd) depot. Each KeyPoint has
+     * @param graph the instance's graph, used to get shortest paths values during key point initialization and later in constraint propagation
+     * @param requestList the instance's request list, used to associate KeyPoints that represent requests with the appropriate informations
+     */
+    explicit SAEVRoute(const Graph &graph, const std::vector<Request> &requestList);
+    /**
+     * Initializes a route following a tabular doubly chained approach where 0->nbRequest*2-1 are Request Origin (even)/Destination (odd)
+     * and nbRequest*2 -> nbRequest*4-1 are vehicle start (even)/end (odd) depot. Each KeyPoint has
+     * @param graph the instance's graph, used to get shortest paths values during key point initialization and later in constraint propagation
+     * @param requestList the instance's request list, used to associate KeyPoints that represent requests with the appropriate informations
+     * @param initMultimodal true to init the container to a multimodal approach compatible size in advance, false (default) otherwise to just have requests/depots sections
+     */
+    explicit SAEVRoute(const Graph &graph, const std::vector<Request> &requestList, bool initMultimodal);
+
+    [[nodiscard]] const std::vector<SAEVKeyPoint> &getRoute() const {
+        return _route;
+    }
+
+    /**
+     * Raw request route insertion method. Public for debug purposes, but should effectively never be called by an outside member
+     * @param originKp Reference to the origin key point of the request we want to insert in the route
+     * @param originRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede the origin of the request we want to insert
+     * @param destinationRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede the destination of the request we want to insert
+     */
+    void insertRequest(SAEVKeyPoint &originKp, SAEVKeyPoint * originRequestPredecessorKP, SAEVKeyPoint * destinationRequestPredecessorKP);
+    /**
+     * Inserts a request with propagation in a new vehicle while updating last active vehicle ID (since the vehicle is new, no need to tell where to insert)
+     * @param originKp Reference to the origin key point of the request we want to insert in the route
+     */
+    void insertRequestInNewVehicle(SAEVKeyPoint &originKp);
+
+    /**
+     * Raw request removal method. Public for debug purposes, but should effectively never be called by an outside member
+     * @param originKp Reference to the origin key point of the request we want to remove from the route
+     */
+    void removeRequest(SAEVKeyPoint &originKp);
+
+    /**
+     * Removes a given base request from the route and does a complete reset and recomputing of bound propagations
+     * By nature of our propagation making bounds only smaller and no simple rule existing to make our bounds larger after removal,
+     * this method is highly inefficient computationally. A new propagation method or even modelling would be necessary to make it faster
+     * @param requestId a base request ID allowing us to find the origin keypoint to use as removal basis
+     */
+    SAEVRouteChangelist removeRequestWithPropagation(size_t requestId);
+    /**
+     * Removes a given entry/exit sub-request from the route and does a complete reset and recomputing of bound propagations
+     * By nature of our propagation making bounds only smaller and no simple rule existing to make our bounds larger after removal,
+     * this method is highly inefficient computationally. A new propagation method or even modelling would be necessary to make it faster
+     * @param requestId a base request ID allowing us to find the entry/exit subrequest to delete
+     * @param isEntry true iff the subrequest to delete
+     */
+    SAEVRouteChangelist removeRequestWithPropagation(size_t requestId, bool isEntry);
+    /**
+     * Removes a given request from the route and does a complete reset and recomputing of bound propagations
+     * By nature of our propagation making bounds only smaller and no simple rule existing to make our bounds larger after removal,
+     * this method is highly inefficient computationally. A new propagation method or even modelling would be necessary to make it faster
+     * @param originKP pointer to the origin key point of a given request to remove
+     */
+    SAEVRouteChangelist removeRequestWithPropagation(SAEVKeyPoint &originKP);
+
+    /**
+     * Updates weight in-between request's Origin/Destination (both included) to account for the given request's weight
+     * ⚠️ Execute this function *after* adding the request to the route, as it assumes the request is part of the route
+     * @param requestOriginKeyPoint reference to the origin keypoint of the request for which we wish to remove the weight to the route
+     */
+    void addRequestWeightToRoute(SAEVKeyPoint &requestOriginKeyPoint);
+    /**
+     * Resets current weight to 0 on the request Origin/Destination key points and decreases weight on the nodes in-between by the request's weight
+     * ⚠️ Execute this function *before* removing the request from the route, as it assumes the request is still part of the route
+     * @param requestOriginKeyPoint reference to the origin keypoint of the request for which we wish to remove the weight to the route
+     */
+    void removeRequestWeightFromRoute(SAEVKeyPoint& requestOriginKeyPoint);
+
+    /**
+     * Tries to insert the request origin and destination next to the given origin/destination predecessors. \n \n
+     * First we verify multiple constraints that don't require actually inserting the request or doing constraint propagation. \n
+     * Then we propagate our min/max bounds, iteratively rippling through every modification induced by min/max neighbour constraints or delta constraints. \n
+     * ⚠️ In case of infeasibility, tryAdd automatically reverts changes and the change list will be effectively empty, but otherwise it's the caller's responsibility to revert changes if necessary
+     * @param requestId Identifier/index in the instance's request vector for the request we wish to insert
+     * @param originRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's origin in the route
+     * @param destinationRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's destination in the route
+     * @return A change list with every min/max bound change made during the tryAdd procedure and a score estimating insertion quality (lower is better)
+     */
+    SAEVRouteChangelist tryAddRequest(size_t requestId, SAEVKeyPoint * originRequestPredecessorKP, SAEVKeyPoint * destinationRequestPredecessorKP);
+
+    /**
+     * Tries to insert the request origin and destination next to the given origin/destination predecessors. \n \n
+     * First we verify multiple constraints that don't require actually inserting the request or doing constraint propagation. \n
+     * Then we propagate our min/max bounds, iteratively rippling through every modification induced by min/max neighbour constraints or delta constraints. \n
+     * ⚠️ In case of infeasibility, tryAdd automatically reverts changes and the change list will be effectively empty, but otherwise it's the caller's responsibility to revert changes if necessary
+     * @param requestOriginKeyPoint KeyPoint reference pointing to the origin KP of the request we wish to insert
+     * @param originRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's origin in the route
+     * @param destinationRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's destination in the route
+     * @return A change list with every min/max bound change made during the tryAdd procedure and a score estimating insertion quality (lower is better)
+     */
+    SAEVRouteChangelist tryAddRequest(SAEVKeyPoint &requestOriginKeyPoint, SAEVKeyPoint * originRequestPredecessorKP, SAEVKeyPoint * destinationRequestPredecessorKP);
+
+    /**
+     * Verifies time window constraints on our request's origin/destination's projected neighbour, aka originPredecessor/destinationPredecessor and their successor. <br>
+     * There is a special case taken into account if originPredecessor and destinationPredecessor are the same KeyPoint, since then, Origin's successor would be Destination <br>
+     * <br>
+     * ⚠️ Weight constraints are checked separately <br>
+     * ⚠️ Those checks don't modify the route => no rollback is needed at this point
+     * @param originKP Reference to the origin key point of our request, necessary to retrieve the appropriate key points
+     * @param originNodeIndex The request's origin node index, necessary to compute travel times
+     * @param destinationNodeIndex The request's destination node index, necessary to compute travel times
+     * @param originPredecessor The origin's expected predecessor, aka the point after which we wish to insert our origin
+     * @param destinationPredecessor The destination's expected predecessor, aka the point after which we wish to insert our destination
+     * @return true iff all neighbouring time window conditions are valid at our insertion points, false otherwise
+     */
+    bool doNeighbouringTWChecks(const SAEVKeyPoint &originKP, const SAEVKeyPoint *originPredecessor,
+                                const SAEVKeyPoint *destinationPredecessor) const;
+
+    /**
+     * Method called after having validated conditions not requiring request insertion. \n
+     * This will effectively insert our procedure and ripple through bound changes. \n
+     * If the bounds become infeasible (min > max), then the propagation stops with a changelist with score= +Infinity and changes will be immediately reverted.
+     * Otherwise, it's the responsibility of this method's callers to revert changes if wanted (or to defer this responsibility to its caller)
+     * @param originKP Reference to the origin key point for the request we wish to insert
+     * @param originRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's origin in the route
+     * @param destinationRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's destination in the route
+     * @return A change list with every min/max bound change made during the insert procedure and a score estimating insertion quality (lower is better)
+     */
+    SAEVRouteChangelist insertRequestWithPropagation(SAEVKeyPoint &originKP, SAEVKeyPoint * originRequestPredecessorKP, SAEVKeyPoint * destinationRequestPredecessorKP);
+
+    /**
+     * Executes the bound propagation algorithm, starting from the given bound propagation queue.
+     * Each item in the queue references which bound (Min/Max) and what key point was affected, and from there we verify bounds for each predecessor/successor
+     *
+     * If the bounds become infeasible (min > max), then the propagation stops with a changelist with score= +Infinity and changes will be immediately reverted.
+     * Otherwise, it's the responsibility of this method's callers to revert changes if wanted (or to defer this responsibility to its caller)
+     *
+     * @param boundPropagationQueue a (Bound/Keypoint) pair queue giving a list of starting points for propagation. Typically when inserting a key point, the queue contains two items (one for each bound)
+     * @return A change list with every min/max bound change made during the insert procedure and a score estimating insertion quality (lower is better)
+     */ // FIXME: not a fan of using int instead of the Bound enum here, but I can't forward-declare it since it's part of SAEVRouteChange
+    SAEVRouteChangelist doBoundPropagation(std::queue<std::pair<int, SAEVKeyPoint *>> &boundPropagationQueue,
+                                           SAEVRouteChangelist &changelist);
+    /**
+     * Returns a score value equivalent to the detour implied by insertion of a request after the two given key point indexes.
+     * The specific case of origin/destination being inserted one after another is taken into account
+     * ⚠️ This method assumes the insertion hasn't been done yet, so compute detour scores before inserting your request ⚠️
+     * @param originKP Reference to the origin key point for the request we wish to insert
+     * @param originRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's origin in the route
+     * @param destinationRequestPredecessorKP Identifier/index in the route key point vector for the request origin or destination that will precede our request's destination in the route
+     * @return
+     */
+    double getDetourScore(const SAEVKeyPoint &originKP, const SAEVKeyPoint * originRequestPredecessorKP, const SAEVKeyPoint * destinationRequestPredecessorKP) const;
+
+    BestInsertionQueue getBestInsertionsQueue(size_t requestId, size_t vehicleId);
+    BestInsertionQueue getBestInsertionsQueue(SAEVKeyPoint &originKP, size_t vehicleId);
+    BestInsertionQueue getBestFeasibleInsertionsQueue(size_t requestId, size_t vehicleId);
+    BestInsertionQueue getBestFeasibleInsertionsQueue(SAEVKeyPoint &originKP, size_t vehicleId);
+    void getBestFeasibleInsertionsQueue(BestInsertionQueue& bestInsertionQueue, const SAEVKeyPoint &requestOriginKeyPoint, size_t vehicleId);
+
+    SAEVKeyPoint& getRequestOrigin(const size_t requestId) { return _route[getRequestOriginRouteIdx(requestId)];}
+    SAEVKeyPoint& getRequestDestination(const size_t requestId) { return _route[getRequestDestinationRouteIdx(requestId)];}
+
+    SAEVKeyPoint& getOriginDepot(const size_t vehicleId) { return _route[getOriginDepotRouteIdx(vehicleId)];}
+    SAEVKeyPoint& getDestinationDepot(const size_t vehicleId) { return _route[getDestinationDepotRouteIdx(vehicleId)];}
+
+    [[nodiscard]] size_t getRequestOriginRouteIdx(const size_t requestId) const { return requestId * 2;}
+    [[nodiscard]] size_t getRequestDestinationRouteIdx(const size_t requestId) const { return requestId * 2 + 1;}
+    [[nodiscard]] size_t getRequestOriginNodeIdx(const size_t requestId) const { return _route[getRequestOriginRouteIdx(requestId)].getNodeIndex();}
+    [[nodiscard]] size_t getRequestDestinationNodeIdx(const size_t requestId) const { return _route[getRequestDestinationRouteIdx(requestId)].getNodeIndex();}
+
+    [[nodiscard]] size_t getOriginDepotRouteIdx(const size_t vehicleId) const { return _nbRequest*2 + vehicleId*2;}
+    [[nodiscard]] size_t getDestinationDepotRouteIdx(const size_t vehicleId) const { return _nbRequest*2 + vehicleId*2 + 1;}
+    [[nodiscard]] size_t getOriginDepotNodeIdx(const size_t vehicleId) const { return _route[getOriginDepotRouteIdx(vehicleId)].getNodeIndex();}
+    [[nodiscard]] size_t getDestinationDepotNodeIdx(const size_t vehicleId) const { return _route[getDestinationDepotRouteIdx(vehicleId)].getNodeIndex();}
+
+    [[nodiscard]] size_t getEntrySubRequestOriginRouteIdx(const size_t requestId) const { return _nbRequest * 4 + requestId * 2;}
+    [[nodiscard]] size_t getEntrySubRequestDestinationRouteIdx(const size_t requestId) const { return _nbRequest * 4 + requestId * 2 + 1;}
+    [[nodiscard]] SAEVKeyPoint& getEntrySubRequestOrigin(const size_t requestId) { return _route[getEntrySubRequestOriginRouteIdx(requestId)];}
+    [[nodiscard]] SAEVKeyPoint& getEntrySubRequestDestination(const size_t requestId) { return _route[getEntrySubRequestDestinationRouteIdx(requestId)];}
+    [[nodiscard]] size_t getEntrySubRequestOriginNodeIdx(const size_t requestId) const { return _route[getEntrySubRequestOriginRouteIdx(requestId)].getNodeIndex();}
+    [[nodiscard]] size_t getEntrySubRequestDestinationNodeIdx(const size_t requestId) const { return _route[getEntrySubRequestDestinationRouteIdx(requestId)].getNodeIndex();}
+
+    [[nodiscard]] size_t getExitSubRequestOriginRouteIdx(const size_t requestId) const { return _nbRequest * 6 + requestId * 2;}
+    [[nodiscard]] size_t getExitSubRequestDestinationRouteIdx(const size_t requestId) const { return _nbRequest * 6 + requestId * 2 + 1;}
+    [[nodiscard]] SAEVKeyPoint& getExitSubRequestOrigin(const size_t requestId) { return _route[getExitSubRequestOriginRouteIdx(requestId)];}
+    [[nodiscard]] SAEVKeyPoint& getExitSubRequestDestination(const size_t requestId) { return _route[getExitSubRequestDestinationRouteIdx(requestId)];}
+    [[nodiscard]] size_t getExitSubRequestOriginNodeIdx(const size_t requestId) const { return _route[getExitSubRequestOriginRouteIdx(requestId)].getNodeIndex();}
+    [[nodiscard]] size_t getExitSubRequestDestinationNodeIdx(const size_t requestId) const { return _route[getExitSubRequestDestinationRouteIdx(requestId)].getNodeIndex();}
+
+    [[nodiscard]] size_t getLastActiveVehicleId() const { return _lastActiveVehicleId; }
+    size_t addNewActiveVehicle() {
+        assertm(_lastActiveVehicleId < _nbRequest - 1, "No more vehicle available");
+        return ++_lastActiveVehicleId;
+    }
+    void setLastActiveVehicleId(size_t lastActiveVehicleId) { _lastActiveVehicleId = lastActiveVehicleId; }
+
+    /**
+     * Verifies that time windows have been properly propagated for a given vehicle's route
+     * @param vehicleId
+     * @return true iff all time windows have been properly shrunk
+     */
+    bool checkRouteTimeWindows(size_t vehicleId);
+
+    /**
+     * @return A string comprised of every vehicle appended to their own line each
+     */
+    std::string to_string();
+    /**
+     * @param vehicleId ID of the vehicle for which we want to print the current route
+     * @return A string comprised of every keypoint traversed by the vehicle corresponding to the given vehicleId
+     */
+    std::string to_string(size_t vehicleId);
+    void exportToFile();
+
+    SAEVKeyPoint &getSubRequestOrigin(size_t baseRequestId, bool isEntry);
+};
+
+#include "propagation/SAEVRouteChangelist.h"
+
+#endif //GREEDYALGORITHM_SAEVROUTE_H
diff --git a/src/routes/vehicle/propagation/SAEVRouteChange.cpp b/src/routes/vehicle/propagation/SAEVRouteChange.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..63894fd1a30e6d15f3e5797a966ad55cd2dac144
--- /dev/null
+++ b/src/routes/vehicle/propagation/SAEVRouteChange.cpp
@@ -0,0 +1,21 @@
+//
+// Created by romain on 23/04/24.
+//
+
+#include "SAEVRouteChange.h"
+
+void SAEVRouteChange::applyChange() {
+    if(_bound == Min) {
+        _kpPointer->setMinTw(_kpPointer->getMinTw() + _value);
+    } else {
+        _kpPointer->setMaxTw(_kpPointer->getMaxTw() - _value);
+    }
+}
+
+void SAEVRouteChange::revertChange() {
+    if(_bound == Min) {
+        _kpPointer->setMinTw(_kpPointer->getMinTw() - _value);
+    } else {
+        _kpPointer->setMaxTw(_kpPointer->getMaxTw() + _value);
+    }
+}
diff --git a/src/routes/vehicle/propagation/SAEVRouteChange.h b/src/routes/vehicle/propagation/SAEVRouteChange.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac932e6ef4c44d36bb75d96404059a1d9a31daac
--- /dev/null
+++ b/src/routes/vehicle/propagation/SAEVRouteChange.h
@@ -0,0 +1,34 @@
+//
+// Created by romain on 23/04/24.
+//
+
+#ifndef GREEDYALGORITHM_SAEVROUTECHANGE_H
+#define GREEDYALGORITHM_SAEVROUTECHANGE_H
+
+enum Bound {
+    Min, Max
+};
+
+class SAEVKeyPoint;
+class SAEVRouteChange {
+private:
+    SAEVKeyPoint* const _kpPointer;
+    const Bound _bound;
+    const int _value;
+public:
+    SAEVRouteChange(SAEVKeyPoint& kp, Bound bound, int value) : _kpPointer(&kp), _bound(bound), _value(value) {};
+    /**
+     * Unconditionally applies the bound change on the appropriate key point
+     * (adds value to the min bound or subtracts value to the max bound)
+     */
+    void applyChange();
+    /**
+     * Unconditionally reverts the bound change on the appropriate key point
+     * (subtracts value to the min bound or adds value to the max bound)
+     */
+    void revertChange();
+};
+
+#include "../SAEVKeyPoint.h"
+
+#endif //GREEDYALGORITHM_SAEVROUTECHANGE_H
diff --git a/src/routes/vehicle/propagation/SAEVRouteChangelist.cpp b/src/routes/vehicle/propagation/SAEVRouteChangelist.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a289b2654aeaf7346b00f5c4aa122c1d2390c804
--- /dev/null
+++ b/src/routes/vehicle/propagation/SAEVRouteChangelist.cpp
@@ -0,0 +1,142 @@
+//
+// Created by romain on 23/04/24.
+//
+
+#include "SAEVRouteChangelist.h"
+
+const std::vector<SAEVRouteChange> &SAEVRouteChangelist::getChangelist() const {
+    return _changelist;
+}
+
+void SAEVRouteChangelist::push_back(SAEVRouteChange change) {
+    _changelist.push_back(change);
+}
+
+void SAEVRouteChangelist::emplace_back(SAEVRouteChange change) {
+    _changelist.emplace_back(change);
+}
+
+void SAEVRouteChangelist::emplace_back(SAEVKeyPoint &kp, Bound bound, uint value) {
+    _changelist.emplace_back(kp, bound, value);
+}
+
+void SAEVRouteChangelist::applyChanges() {
+    if(_requestOriginKeyPoint != nullptr && _originPredecessorKP != nullptr
+    && _destinationPredecessorKP != nullptr && _status < InsertionStatus::FAILURE_PRECONDITION_TW) {
+        _routePtr->insertRequest(*_requestOriginKeyPoint, _originPredecessorKP, _destinationPredecessorKP);
+    }
+    for(SAEVRouteChange change : _changelist) {
+        change.applyChange();
+    }
+    _currentStatus = _status;
+}
+
+void SAEVRouteChangelist::revertChanges() {
+    if(_requestOriginKeyPoint != nullptr && _originPredecessorKP != nullptr
+    && _destinationPredecessorKP != nullptr && _status < InsertionStatus::FAILURE_PRECONDITION_TW) {
+        _routePtr->removeRequest(*_requestOriginKeyPoint);
+    }
+    for(SAEVRouteChange change : _changelist) {
+        change.revertChange();
+    }
+    _currentStatus = InsertionStatus::CHANGELIST_REVERTED;
+}
+
+SAEVKeyPoint *SAEVRouteChangelist::getRequestOriginKeyPoint() const {
+    return _requestOriginKeyPoint;
+}
+
+const SAEVKeyPoint * SAEVRouteChangelist::getOriginPredecessorKP() const {
+    return _originPredecessorKP;
+}
+
+const SAEVKeyPoint * SAEVRouteChangelist::getDestinationPredecessorKP() const {
+    return _destinationPredecessorKP;
+}
+
+double SAEVRouteChangelist::getScore() const {
+    return _score;
+}
+
+void SAEVRouteChangelist::setScore(double score) {
+    SAEVRouteChangelist::_score = score;
+}
+
+bool SAEVRouteChangelist::operator>(const SAEVRouteChangelist &rhs) const {
+    return _routePtr == rhs.getRoutePtr() && _score > rhs.getScore();
+}
+
+bool SAEVRouteChangelist::operator<(const SAEVRouteChangelist &rhs) const {
+    return _routePtr == rhs.getRoutePtr() && _score < rhs.getScore();
+}
+
+SAEVRoute * SAEVRouteChangelist::getRoutePtr() const {
+    return _routePtr;
+}
+
+SAEVRouteChangelist::InsertionStatus SAEVRouteChangelist::getStatus() const {
+    return _status;
+}
+
+std::string SAEVRouteChangelist::getStatusString() const {
+    std::string res;
+
+    switch (_status) {
+        case InsertionStatus::SUCCESS:
+            res = "SUCCESS";
+            break;
+        case InsertionStatus::FAILURE_MIN:
+            res = "FAILURE_MIN";
+            break;
+        case InsertionStatus::FAILURE_MAX:
+            res = "FAILURE_MAX";
+            break;
+        case InsertionStatus::FAILURE_DELTA_MIN:
+            res = "FAILURE_DELTA_MIN";
+            break;
+        case InsertionStatus::FAILURE_DELTA_MAX:
+            res = "FAILURE_DELTA_MAX";
+            break;
+        case InsertionStatus::FAILURE_PRECONDITION_TW:
+            res = "FAILURE_PRECONDITION_TW";
+            break;
+        case InsertionStatus::FAILURE_PRECONDITION_WEIGHT:
+            res = "FAILURE_PRECONDITION_WEIGHT";
+            break;
+        case InsertionStatus::CHANGELIST_REVERTED:
+            res = "CHANGELIST_REVERTED";
+            break;
+        default:
+            res = "UNKNOWN STATUS";
+    }
+
+    return res;
+}
+
+SAEVRouteChangelist::InsertionStatus SAEVRouteChangelist::getCurrentStatus() const {
+    return _currentStatus;
+}
+
+bool SAEVRouteChangelist::shouldUndoInsertion() const {
+    return _currentStatus < InsertionStatus::FAILURE_PRECONDITION_TW;
+}
+
+void SAEVRouteChangelist::setStatus(SAEVRouteChangelist::InsertionStatus status) {
+    _status = status;
+}
+
+void SAEVRouteChangelist::setCurrentStatus(SAEVRouteChangelist::InsertionStatus currentStatus) {
+    _currentStatus = currentStatus;
+}
+
+size_t SAEVRouteChangelist::getVehicleId() const {
+    return _vehicleId;
+}
+
+void SAEVRouteChangelist::setVehicleId(size_t vehicleId) {
+    _vehicleId = vehicleId;
+}
+
+bool SAEVRouteChangelist::success() const {
+    return _status == InsertionStatus::SUCCESS;
+}
diff --git a/src/routes/vehicle/propagation/SAEVRouteChangelist.h b/src/routes/vehicle/propagation/SAEVRouteChangelist.h
new file mode 100644
index 0000000000000000000000000000000000000000..13a043c65c852f3f694f77068999ebdd95533077
--- /dev/null
+++ b/src/routes/vehicle/propagation/SAEVRouteChangelist.h
@@ -0,0 +1,133 @@
+//
+// Created by romain on 23/04/24.
+//
+
+#ifndef GREEDYALGORITHM_SAEVROUTECHANGELIST_H
+#define GREEDYALGORITHM_SAEVROUTECHANGELIST_H
+
+
+#include "SAEVRouteChange.h"
+#include "../SAEVRoute.h"
+
+/**
+ * This class serves as iterative memory for changes made to key points during constraint propagation. \n
+ * It memorizes the data to undo/redo request insertion and apply/revert changes made without losing time on duplicate feasibility checks. \n
+ * To allow comparison and ordering between multiple insertions if necessary, it also memorizes a score associated with this constraint propagation (lower is better)
+ */
+class SAEVRouteChangelist {
+public:
+    enum class InsertionStatus{
+        SUCCESS,
+        FAILURE_MIN,
+        FAILURE_MAX,
+        FAILURE_DELTA_MIN,
+        FAILURE_DELTA_MAX,
+        FAILURE_PRECONDITION_TW, //To check if you need to remove a request, check status > FAILURE_PRECONDITION_WEIGHT
+        FAILURE_PRECONDITION_WEIGHT,
+        CHANGELIST_REVERTED
+    };
+private:
+    //Change target info
+    SAEVRoute * _routePtr{};
+    SAEVKeyPoint* _requestOriginKeyPoint{};
+    size_t _vehicleId{};
+    SAEVKeyPoint* _originPredecessorKP{};
+    SAEVKeyPoint* _destinationPredecessorKP{};
+
+    //Changelist specific info (aka, if this ever becomes a template class, *this* is what will go in the generic interface)
+    std::vector<SAEVRouteChange> _changelist{};
+    double _score{std::numeric_limits<double>::infinity()}; //Init score to infinity
+
+    InsertionStatus _status{InsertionStatus::CHANGELIST_REVERTED}; //Status on changelist creation
+    InsertionStatus _currentStatus; //Updated status after applying/reverting changelist
+
+public:
+
+    SAEVRouteChangelist(SAEVRoute * routePtr, SAEVKeyPoint *requestOriginKeyPoint) : _routePtr(routePtr), _requestOriginKeyPoint(requestOriginKeyPoint) {}
+
+    /**
+     * Initializes a change list to memorize every iterative modification made during constraint propagation
+     * @param routePtr a pointer to the route the constraint propagation was applied on. revert/apply operations will be done on this route
+     * @param requestOriginKeyPoint Reference to the request origin key point in the route
+     * @param originPredecessorKP Reference to the request KP our origin will be inserted after
+     * @param destinationPredecessorKP Reference to the request KP our destination will be inserted after
+     */
+    explicit SAEVRouteChangelist(SAEVRoute * const routePtr, SAEVKeyPoint *requestOriginKeyPoint, SAEVKeyPoint* originPredecessorKP, SAEVKeyPoint* destinationPredecessorKP, InsertionStatus status)
+    : _routePtr(routePtr), _requestOriginKeyPoint(requestOriginKeyPoint), _originPredecessorKP(originPredecessorKP), _destinationPredecessorKP(destinationPredecessorKP), _status(status), _currentStatus(status) {};
+
+
+    /**
+     * @return A pointer to the route this change list applies/reverts changes to
+     */
+    [[nodiscard]] SAEVRoute * getRoutePtr() const;
+
+    [[nodiscard]] const std::vector<SAEVRouteChange> &getChangelist() const;
+
+    SAEVKeyPoint *getRequestOriginKeyPoint() const;
+
+    [[nodiscard]] size_t getVehicleId() const;
+    void setVehicleId(size_t vehicleId);
+
+    [[nodiscard]] const SAEVKeyPoint * getOriginPredecessorKP() const;
+
+    [[nodiscard]] const SAEVKeyPoint * getDestinationPredecessorKP() const;
+
+    [[nodiscard]] InsertionStatus getStatus() const;
+    void setStatus(InsertionStatus status);
+    [[nodiscard]] bool success() const;
+
+    [[nodiscard]] InsertionStatus getCurrentStatus() const;
+    void setCurrentStatus(InsertionStatus currentStatus);
+
+    /**
+     * @return A score value associated with this changelist. A lower score is better
+     */
+    [[nodiscard]] double getScore() const;
+
+    /**
+     * @return True iff the current state of the route wrt change list requires to remove undo an insertion
+     */
+    [[nodiscard]] bool shouldUndoInsertion() const;
+
+    /**
+     * Updates this change list's score value if needed (namely, after the whole propagation has been done)
+     * @param score A value representing how good the insertion associated with this change list is (lower is better)
+     */
+    void setScore(double score);
+
+    void push_back(SAEVRouteChange change);
+    void emplace_back(SAEVRouteChange change);
+    void emplace_back(SAEVKeyPoint& kp, Bound bound, uint value);
+
+    /**
+     * Inserts the request associated to this changelist to the given route and iteratively applies every change memorized in this changelist.
+     * Changelist status is updated to reflect the original value associated with the changes
+     * Aside from OOB exceptions, no checks are done in this method
+     */
+    void applyChanges();
+    /**
+     * removes the request associated to this changelist from the given route and iteratively reverts every change memorized in this changelist.
+     * Aside from OOB exceptions, no checks are done in this method
+     */
+    void revertChanges();
+
+    /**
+     * @param rhs the value *this* will be compared to
+     * @return True iff this changelist's score is strictly higher than rhs' score
+     */
+    bool operator>(const SAEVRouteChangelist& rhs) const;
+    /**
+     * @param rhs the value *this* will be compared to
+     * @return True iff this changelist's score is strictly lower than rhs' score
+     */
+    bool operator<(const SAEVRouteChangelist& rhs) const;
+
+    /**
+     * Formats the current changelist status as a string for logging purposes
+     * @return A string corresponding to the current status
+     */
+    [[nodiscard]] std::string getStatusString() const;
+};
+
+
+#endif //GREEDYALGORITHM_SAEVROUTECHANGELIST_H
diff --git a/src/services/visualisation/graph_vis.py b/src/services/visualisation/graph_vis.py
deleted file mode 100644
index 5c0ca483c0a9afb068db30a8c88f460eea5aa436..0000000000000000000000000000000000000000
--- a/src/services/visualisation/graph_vis.py
+++ /dev/null
@@ -1,78 +0,0 @@
-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/Constants.h b/src/utils/Constants.h
new file mode 100644
index 0000000000000000000000000000000000000000..917db25fcd670091bf931b5c409ae651e8bf17f6
--- /dev/null
+++ b/src/utils/Constants.h
@@ -0,0 +1,34 @@
+//
+// Created by romain on 24/06/24.
+//
+
+#ifndef GREEDYALGORITHM_CONSTANTS_H
+#define GREEDYALGORITHM_CONSTANTS_H
+
+
+class Constants {
+public:
+    static constexpr uint VEHICLE_CAPACITY = 4;
+    static constexpr uint MAX_TRANSIT_CONNECTIONS = 2;
+    /**
+     * The max amount of closest stations we preprocess for a given node (namely request origin nodes)
+     */
+    static constexpr size_t MAX_CLOSEST_STATIONS_CANDIDATES = 30;
+    /**
+     * The maximum amount of transit entry stations checked during our heuristic algorithm
+     */
+    static constexpr size_t MAX_TRANSIT_ENTRY_CANDIDATES = 10;
+    /**
+     * The maximum amount of transit exit stations checked during our heuristic algorithm
+     */
+    static constexpr size_t MAX_TRANSIT_EXIT_CANDIDATES = 10;
+    static constexpr double DEFAULT_DELTA_RATIO = 1.5;
+/**
+     * A ratio between 1 and deltaRatio that'll allow us to compute each
+     * request's transit travel time ratio with regards to its delta ratio
+     */
+    static constexpr double BASE_TRANSIT_TRAVEL_TIME_RATIO = 1.4;
+};
+
+
+#endif //GREEDYALGORITHM_CONSTANTS_H
diff --git a/src/utils/Globals.h b/src/utils/Globals.h
new file mode 100644
index 0000000000000000000000000000000000000000..3beb725947cfc42381b31e072fd65f575cfe8356
--- /dev/null
+++ b/src/utils/Globals.h
@@ -0,0 +1,16 @@
+//
+// Created by Romain on 19/07/2024.
+//
+
+#ifndef GREEDYALGORITHM_GLOBALS_H
+#define GREEDYALGORITHM_GLOBALS_H
+
+// uncomment to disable assert()
+// #define NDEBUG
+#include <cassert>
+#define assertm(exp, msg) assert(((void)msg, exp))
+
+using uint = unsigned int;
+using ulong = unsigned long;
+
+#endif //GREEDYALGORITHM_GLOBALS_H
diff --git a/src/utils/Instance Generation/Graph/GraphGenerator.h b/src/utils/Instance Generation/Graph/GraphGenerator.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e8fc976478a7bdb8b5268d6218b60be6b7f7b8b
--- /dev/null
+++ b/src/utils/Instance Generation/Graph/GraphGenerator.h	
@@ -0,0 +1,29 @@
+//
+// Created by romain on 13/06/24.
+//
+
+#ifndef GREEDYALGORITHM_GRAPHGENERATOR_H
+#define GREEDYALGORITHM_GRAPHGENERATOR_H
+
+
+#include "../../../instance/graph/Graph.h"
+#include "PTLineGenerationParameters.h"
+
+class GraphGenerator {
+    /**
+     * Overwrites the node vector in the given graph with data imported from the given file in a CSV-like format (status,x,y)
+     * @param baseGraph The graph in which to update node data
+     * @param nodeFile The file containing the required data. Format : (status,x,y)
+     */
+    static void importNodeFromCSV(Graph baseGraph, std::filesystem::path nodeFile);
+    static void generateNodes(Graph baseGraph, uint maxX, uint maxY, uint nbNodes) {
+
+    }
+    static void importDistanceMatrixFromCSV(Graph baseGraph, std::filesystem::path nodeFile);
+    static void generateDistanceMatrix(Graph baseGraph, std::filesystem::path nodeFile);
+    static void importPTNetworkFromCSV(Graph baseGraph, std::filesystem::path ptNetworkFile);
+    static void generatePTNetwork(Graph baseGraph, PTLineGenerationParameters parameters);
+};
+
+
+#endif //GREEDYALGORITHM_GRAPHGENERATOR_H
diff --git a/src/utils/Instance Generation/Graph/OSRMGraphGenerator.h b/src/utils/Instance Generation/Graph/OSRMGraphGenerator.h
new file mode 100644
index 0000000000000000000000000000000000000000..5e3bbf70e9729000c33446729e3df7098a44cfec
--- /dev/null
+++ b/src/utils/Instance Generation/Graph/OSRMGraphGenerator.h	
@@ -0,0 +1,17 @@
+//
+// Created by romain on 12/06/24.
+//
+
+#ifndef GREEDYALGORITHM_OSRMGRAPHGENERATOR_H
+#define GREEDYALGORITHM_OSRMGRAPHGENERATOR_H
+
+
+#include "../../../instance/graph/Graph.h"
+
+class OSRMGraphGenerator {
+    static void initOSRMEngine();
+    static Graph createMatrixFromOSRM(Graph graph);
+};
+
+
+#endif //GREEDYALGORITHM_OSRMGRAPHGENERATOR_H
diff --git a/src/utils/Instance Generation/Graph/PTLineGenerationParameters.h b/src/utils/Instance Generation/Graph/PTLineGenerationParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..05a1970b63c411f40abf35f37faa7bb1f791b78e
--- /dev/null
+++ b/src/utils/Instance Generation/Graph/PTLineGenerationParameters.h	
@@ -0,0 +1,14 @@
+//
+// Created by romain on 18/06/24.
+//
+
+#ifndef GREEDYALGORITHM_PTLINEGENERATIONPARAMETERS_H
+#define GREEDYALGORITHM_PTLINEGENERATIONPARAMETERS_H
+
+
+class PTLineGenerationParameters {
+
+};
+
+
+#endif //GREEDYALGORITHM_PTLINEGENERATIONPARAMETERS_H
diff --git a/src/utils/Instance Generation/Requests/RequestGenerationParameters.h b/src/utils/Instance Generation/Requests/RequestGenerationParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..e400a10e1fd5965f90dd0c6b40dbce7869d90bff
--- /dev/null
+++ b/src/utils/Instance Generation/Requests/RequestGenerationParameters.h	
@@ -0,0 +1,121 @@
+//
+// Created by romain on 12/06/24.
+//
+
+#ifndef GREEDYALGORITHM_REQUESTGENERATIONPARAMETERS_H
+#define GREEDYALGORITHM_REQUESTGENERATIONPARAMETERS_H
+
+
+#include <cstdlib>
+#include <ostream>
+
+class RequestGenerationParameters {
+private:
+    size_t _requestAmount;
+    double _deltaRatio;
+    uint _deltaMinDuration;
+    uint _timeWindowWidth;
+    uint _periodStartTime;
+    uint _periodEndTime;
+    ulong _rngSeed;
+    //finding a way to add the graph to these parameters would be good, but adding a dependency between graph and request generation parameters doesn't feel right
+public:
+    /**
+     * Generates a request generation parameter container with default parameters (giving a seed is mandatory still !)
+     * Default parameters :
+     *  Delta ratio = 1.25 (users will accept at most a trip 25% longer than the shortest path)
+     *  Time Window width = 30 (users will have a 30 minute range of acceptable arrival dates)
+     *  Delta ratio = 1.25
+     *  Delta min leeway = 10 minutes of minimum possible detour time to calculate delta value
+     *  periodStartTime = 360 (requests start after 6am)
+     *  periodEndTime = 660 (requests end before 11am)
+     * @param requestAmount How many requests to generate
+     * @param rngSeed a seed to ensure reproducibility of the generation
+     */
+    explicit RequestGenerationParameters(size_t requestAmount, ulong rngSeed) : _requestAmount(requestAmount), _deltaRatio(1.25), _deltaMinDuration(10), _timeWindowWidth(30),
+    _periodStartTime(360), _periodEndTime(660), _rngSeed(rngSeed) {}
+
+    /**
+     * Generates a request generation parameter container with the given parameters
+     * @param requestAmount How many requests to generate
+     * @param deltaRatio ratio by which the direct path duration for a request is multiplied to get the max routing time
+     * @param deltaMinDuration minimum delta value added to the direct path duration to get a max delta time
+     * @param timeWindowWidth size of the time window available on arrival
+     * @param periodStartTime start time for the period during which requests can be served
+     * @param periodEndTime end time for the period during which requests can be served
+     * @param rngSeed a seed to ensure reproducibility of the generation
+     */
+    RequestGenerationParameters(size_t requestAmount, double deltaRatio, uint deltaMinDuration, uint timeWindowWidth,
+                                uint periodStartTime, uint periodEndTime, ulong rngSeed) : _requestAmount(
+            requestAmount), _deltaRatio(deltaRatio), _deltaMinDuration(deltaMinDuration), _timeWindowWidth(
+            timeWindowWidth), _periodStartTime(periodStartTime), _periodEndTime(periodEndTime), _rngSeed(rngSeed) {}
+
+    size_t getRequestAmount() const {
+        return _requestAmount;
+    }
+
+    void setRequestAmount(size_t requestAmount) {
+        _requestAmount = requestAmount;
+    }
+
+    double getDeltaRatio() const {
+        return _deltaRatio;
+    }
+
+    void setDeltaRatio(double deltaRatio) {
+        _deltaRatio = deltaRatio;
+    }
+
+    uint getDeltaMinDuration() const {
+        return _deltaMinDuration;
+    }
+
+    void setDeltaMinDuration(uint deltaMinDuration) {
+        _deltaMinDuration = deltaMinDuration;
+    }
+
+    uint getTimeWindowWidth() const {
+        return _timeWindowWidth;
+    }
+
+    void setTimeWindowWidth(uint timeWindowWidth) {
+        _timeWindowWidth = timeWindowWidth;
+    }
+
+    uint getPeriodStartTime() const {
+        return _periodStartTime;
+    }
+
+    void setPeriodStartTime(uint periodStartTime) {
+        _periodStartTime = periodStartTime;
+    }
+
+    uint getPeriodEndTime() const {
+        return _periodEndTime;
+    }
+
+    void setPeriodEndTime(uint periodEndTime) {
+        _periodEndTime = periodEndTime;
+    }
+
+    ulong getRngSeed() const {
+        return _rngSeed;
+    }
+
+    void setRngSeed(ulong rngSeed) {
+        _rngSeed = rngSeed;
+    }
+
+    friend std::ostream &operator<<(std::ostream &os, const RequestGenerationParameters &parameters) {
+        os << "_requestAmount: " << parameters._requestAmount << "\n_deltaRatio: " << parameters._deltaRatio
+           << "\n_deltaMinDuration: " << parameters._deltaMinDuration << "\n_timeWindowWidth: "
+           << parameters._timeWindowWidth << "\n_periodStartTime: " << parameters._periodStartTime << "\n_periodEndTime: "
+           << parameters._periodEndTime << "\n_rngSeed: " << parameters._rngSeed;
+        return os;
+    }
+
+    bool operator==(const RequestGenerationParameters &rhs) const = default;
+};
+
+
+#endif //GREEDYALGORITHM_REQUESTGENERATIONPARAMETERS_H
diff --git a/src/utils/Instance Generation/Requests/RequestsGenerator.cpp b/src/utils/Instance Generation/Requests/RequestsGenerator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f6fa0d939aa56d1c4348444daf60281a8da9dc14
--- /dev/null
+++ b/src/utils/Instance Generation/Requests/RequestsGenerator.cpp	
@@ -0,0 +1,68 @@
+//
+// Created by romain on 11/06/24.
+//
+
+#include <filesystem>
+#include <fstream>
+#include "RequestsGenerator.h"
+#include "RequestGenerationParameters.h"
+
+namespace fs = std::filesystem;
+
+std::vector<Request>
+RequestsGenerator::generateRequests(const Graph &graph, RequestGenerationParameters generationParameters) {
+    size_t requestAmount = generationParameters.getRequestAmount();
+    double deltaRatio = generationParameters.getDeltaRatio();
+    double deltaMinDuration = generationParameters.getDeltaMinDuration();
+    uint timeWindowWidth = generationParameters.getTimeWindowWidth();
+
+    std::vector<Request> requests;
+    requests.reserve(requestAmount);
+    std::mt19937 rng(generationParameters.getRngSeed());
+    std::uniform_int_distribution<uint32_t> requestTWStartDistribution(generationParameters.getPeriodStartTime() + timeWindowWidth,
+                                                                       generationParameters.getPeriodEndTime() - timeWindowWidth); //reduce period window time to account for TW width
+
+    std::uniform_int_distribution<ulong> nodeDistribution(0, graph.getNbNodes() - 1);
+
+    //Init request variables
+    uint deltaTime;
+    uint requestTWStart;
+    ulong originNodeIdx, destinationNodeIdx;
+    for(size_t i = 0; i < requestAmount; ++i) {
+        //Generate new values
+        requestTWStart = requestTWStartDistribution(rng);
+        originNodeIdx = nodeDistribution(rng);
+        destinationNodeIdx = nodeDistribution(rng);
+        while (destinationNodeIdx == originNodeIdx) { //retry random node as much as needed for origin to be different from destination
+            destinationNodeIdx = nodeDistribution(rng);
+        }
+        //Set delta time with a guaranteed leeway compared to the shortest path
+        deltaTime = graph.getShortestSAEVPath(originNodeIdx, destinationNodeIdx);
+        if(floor(deltaTime * (deltaRatio - 1)) < deltaMinDuration) {
+            deltaTime = deltaTime + deltaMinDuration;
+        } else {
+            deltaTime = floor(deltaTime * deltaRatio);
+        }
+        requests.emplace_back(originNodeIdx, destinationNodeIdx,
+                              TimeWindow(requestTWStart, requestTWStart + timeWindowWidth), //TODO : could be replaced with a min/max width ?
+                              deltaTime, 1);
+    }
+    return requests;
+}
+
+std::vector<Request>
+RequestsGenerator::generateAndExportRequests(const fs::path &exportFolderPath, const Graph &graph, const RequestGenerationParameters &generationParameters) {
+    std::vector<Request> generatedRequests = generateRequests(graph, generationParameters);
+    //Write requests to files
+    fs::create_directories(exportFolderPath);
+    std::ofstream outfileGraph(exportFolderPath.string() + "requests.txt", std::ofstream::out | std::ofstream::trunc); //open and clear file if it already existed
+    for(Request request : generatedRequests) {
+        outfileGraph << request.to_string_export() << std::endl;
+    }
+    outfileGraph.close();
+    //Export parameters to file
+    std::ofstream outfileParams(exportFolderPath.string() + "requestParameters.txt", std::ofstream::out | std::ofstream::trunc); //open and clear file if it already existed
+    outfileParams << generationParameters;
+    outfileParams.close();
+    return generatedRequests;
+}
diff --git a/src/utils/Instance Generation/Requests/RequestsGenerator.h b/src/utils/Instance Generation/Requests/RequestsGenerator.h
new file mode 100644
index 0000000000000000000000000000000000000000..50ad4f3e0716ef78a28789a3185fd7a18bbf0292
--- /dev/null
+++ b/src/utils/Instance Generation/Requests/RequestsGenerator.h	
@@ -0,0 +1,39 @@
+//
+// Created by romain on 11/06/24.
+//
+
+#ifndef GREEDYALGORITHM_REQUESTSGENERATOR_H
+#define GREEDYALGORITHM_REQUESTSGENERATOR_H
+
+
+#include <vector>
+#include <filesystem>
+#include "../../../instance/requests/Request.h"
+#include "RequestGenerationParameters.h"
+
+namespace fs = std::filesystem;
+
+class RequestsGenerator {
+public:
+
+    /**
+     * Parametrized request generation method.
+     * @param graph the graph on which requests are generated (required for node lists and shortest paths between nodes)
+     * @param generationParameters the list of generation parameters to use for this request generation (see RequestGeberationParameters constructors)
+     * @return a vector of randomly generated requests according to the given parameters
+     */
+    [[nodiscard]] static std::vector<Request> generateRequests(const Graph& graph, RequestGenerationParameters generationParameters);
+
+    /**
+     * Parametrized request generation method with file export.
+     * @param exportFolderPath folder path to which the requests will be exported in a "requests.dat" file, a requestsInfo.txt will be exported with seed/parameter info (will overwrite any existing content)
+     * @param graph the graph on which requests are generated (required for node lists and shortest paths between nodes)
+     * @param generationParameters the list of generation parameters to use for this request generation (see RequestGeberationParameters constructors)
+     * @return a vector of randomly generated requests according to the given parameters
+     */
+    [[nodiscard]] static std::vector<Request> generateAndExportRequests(const fs::path &exportFolderPath, const Graph& graph, const RequestGenerationParameters &generationParameters);
+
+};
+
+
+#endif //GREEDYALGORITHM_REQUESTSGENERATOR_H
diff --git a/src/utils/Python visualiser/final.py b/src/utils/Python visualiser/final.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef60dec588a930d90c975df59fa4a0648fcf9218
--- /dev/null
+++ b/src/utils/Python visualiser/final.py	
@@ -0,0 +1,54 @@
+from graphe import Graphe
+from voiture import lire_fichier_routes, afficher_routes, tracer_routes, recuperer_identifiants_noeuds
+import matplotlib.pyplot as plt
+import random
+import numpy as np
+
+def couleur_aleatoire():
+    return (random.randint(0, 255) / 255, random.randint(0, 255) / 255, random.randint(0, 255) / 255)
+
+def afficher_routes_et_graphe(graphe, routes):
+    fig, ax = plt.subplots()
+    ax.axis('off')
+    
+    graphe.tracer_noeuds(fig, ax)
+
+    liste_identifiants_noeuds = recuperer_identifiants_noeuds(routes)
+    liste_coordonnees = [] 
+    for route in liste_identifiants_noeuds:
+        route_coords = []
+        for identifiant in route:
+            x, y = graphe.get_coordonnees(identifiant + 1)
+            route_coords.append((x, y))
+        liste_coordonnees.append(route_coords)
+        
+    rayon = 0.1 
+    for route in liste_coordonnees:
+        couleur = couleur_aleatoire()
+        for i in range(len(route) - 1):  
+            x_start, y_start = route[i][0], route[i][1]
+            x_end, y_end = route[i + 1][0], route[i + 1][1]
+            
+            """
+            dx = x_end - x_start
+            dy = y_end - y_start
+            distance = np.sqrt(dx**2 + dy**2)
+            if distance != 0:
+                cosinus = dx = distance
+                dy /= distance
+                
+            # normaliser le vecteur = le même vecteur mais avec une norme 1 
+            
+            x_start_adj = x_start + dx * rayon  # on ajoute au début de la flèche le début du vecteur 
+            y_start_adj = y_start + dy * rayon
+            x_end_adj = x_end - dx * rayon  # idem mais on soustrait 
+            y_end_adj = y_end - dy * rayon 
+            """
+            ax.annotate("", xy=(x_end, y_end), xytext=(x_start, y_start), arrowprops=dict(arrowstyle="->", color=couleur))
+            
+            # xy=(x_end, y_end) = position de la pointe de la flèche.
+            # xytext=(x_start, y_start) = position de la base de la flèche
+            
+    ax.set_title('Réseau de transport et routes')
+    fig.savefig("graphe_avec_routes.png")
+    plt.close(fig)
diff --git a/src/utils/Python visualiser/graphe.py b/src/utils/Python visualiser/graphe.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e72d4a28d024b782fc9f364845cdb826990d492
--- /dev/null
+++ b/src/utils/Python visualiser/graphe.py	
@@ -0,0 +1,171 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.patches as patches
+import random
+from matplotlib.patches import Circle, RegularPolygon
+import sys
+import math
+
+def couleur_aleatoire():
+    return (random.randint(0, 255) / 255, random.randint(0, 255) / 255, random.randint(0, 255) / 255)
+
+class Graphe:
+    def __init__(self):
+        self.noeuds = {}  # {"1" : {1, 2}}
+        self.lignes = []
+        self.depot_node = 0
+        self.matrice_temps = []
+
+    def ajouter_noeud(self, identifiant, x, y):   # l'identifiant (commence à 1) du noeud et ses coordonnées 
+        self.noeuds[int(identifiant)] = {'x': x, 'y': y}
+
+    def ajouter_ligne(self, freq, start_time, end_time, noeuds):
+        self.lignes.append({'freq': freq, 'start_time': start_time, 'end_time': end_time, 'noeuds': noeuds}) 
+        
+    def ajouter_matrice(self, temps):
+        self.matrice_temps.append(temps)
+        
+    def get_distance(self, id_noeud1, id_noeud2):
+        index_noeud1 = id_noeud1 - 1
+        index_noeud2 = id_noeud2 - 1
+        distance = self.matrice_temps[index_noeud1][index_noeud2]
+        return distance
+    
+    def get_coordonnees(self, identifiant):
+        if identifiant in self.noeuds:
+            return self.noeuds[identifiant]['x'], self.noeuds[identifiant]['y']
+        else:
+            raise ValueError(f"Le noeud avec l'identifiant {identifiant} n'existe pas.")   
+        
+    def lire_fichier(self, chemin_fichier):
+        with open(chemin_fichier, 'r') as fichier:
+            bool_noeuds = False 
+            bool_ligne = False
+            bool_matrice = False
+            lignes_a_sauter = 0
+
+            for ligne in fichier:
+                ligne = ligne.strip()  # Supprime les espaces blancs
+                if lignes_a_sauter > 0:
+                    lignes_a_sauter -= 1
+                    continue
+                if ligne.startswith("#Params"):
+                    lignes_a_sauter = 1
+                    continue
+                if ligne.startswith("#Nodes format"):
+                    bool_noeuds = True
+                    bool_ligne = False
+                    bool_matrice = False
+                    continue
+                if ligne.startswith("#Matrix"):
+                    bool_noeuds = False 
+                    bool_ligne = False
+                    bool_matrice = True
+                if ligne.startswith("#PT line format"):
+                    bool_noeuds = False
+                    bool_ligne = True
+                    bool_matrice = False
+                    continue 
+                if ligne.startswith("#Depot node index"):
+                    bool_noeuds = False
+                    bool_ligne = False
+                    bool_matrice = False
+                    self.depot_node = int(fichier.readline().strip())
+                    continue 
+                
+                if bool_ligne:
+                    parties = ligne.split(',') 
+                    parties = [str(x) for x in parties]
+                    freq = parties[0]
+                    start_time = parties[1]
+                    end_time = parties[2]
+                    noeuds = parties[3:]
+                    self.ajouter_ligne(freq, start_time, end_time, noeuds)
+
+                if bool_noeuds:
+                    _, x, y = ligne.split(',')   
+                    identifiant = len(self.noeuds) + 1  # commence à 1 
+                    self.ajouter_noeud(identifiant, int(x), int(y))
+                    
+                if bool_matrice:
+                    for i in range(len(self.noeuds)):
+                        ligne_matrice = list(map(int, fichier.readline().strip().split(',')))
+                        self.ajouter_matrice(ligne_matrice)
+                        continue
+                        
+    def tracer_noeuds(self, fig, ax):
+        for identifiant, coordonnees in self.noeuds.items():
+            x = coordonnees['x']
+            y = coordonnees['y']
+            if identifiant == self.depot_node + 1: # tracer le noeud de dépôt avec un triangle noir
+                triangle = RegularPolygon((x, y), numVertices=3, radius=0.1, color='black')
+                ax.add_patch(triangle)
+            else:
+                ax.scatter(x, y, color='blue', label=f'Noeud {identifiant}')   
+                circle = Circle((x, y), radius=0.1, color="red")   # pour que les stations soient représentées par des ronds sur la figure 
+                ax.add_patch(circle)
+                
+                ax.annotate(f'Noeud {identifiant}', (x, y))
+                
+        return fig, ax
+    
+    def tracer_lignes(self, fig, ax):
+        for ligne in self.lignes:
+            liste_noeuds_ligne = list(map(int, ligne["noeuds"]))
+            couleur_aleatoire = couleur_aleatoire()
+            for i in range(len(liste_noeuds_ligne) - 1): 
+                noeud_depart = int(liste_noeuds_ligne[i])
+                noeud_arrive = int(liste_noeuds_ligne[i + 1])
+                x_depart = self.noeuds[noeud_depart]['x']
+                y_depart = self.noeuds[noeud_depart]['y']
+                x_arrive = self.noeuds[noeud_arrive]['x']
+                y_arrive = self.noeuds[noeud_arrive]['y']
+                temps = self.matrice_temps[noeud_depart - 1][noeud_arrive - 1] 
+                ax.plot([x_depart, x_arrive], [y_depart, y_arrive], color=couleur_aleatoire)
+                milieu_x = (x_depart + x_arrive) / 2
+                milieu_y = (y_depart + y_arrive) / 2
+                
+          
+                if x_depart < x_arrive:
+                    h = 'top'  
+                else:
+                    h = 'left'
+                ax.text(milieu_x, milieu_y, f"{temps}", ha=h, va='center')
+                
+        return fig, ax
+    
+    def tracer_arrete(self, ax, x1, y1, x2, y2, couleur):
+        ax.plot([x1, x2], [y1, y2], color=couleur)
+                        
+    def sauvegarder_graphe(self):
+        fig, ax = plt.subplots() 
+        
+        fig, ax = tracer_noeuds(fig, ax)
+        fig, ax = tracer_lignes(fig, ax)
+    
+        # pour que la figure contienne toutes les stations (que ça ne dépasse pas) : 
+        min_x = min(coordonnees['x'] for coordonnees in self.noeuds.values())
+        max_x = max(coordonnees['x'] for coordonnees in self.noeuds.values())
+        min_y = min(coordonnees['y'] for coordonnees in self.noeuds.values())
+        max_y = max(coordonnees['y'] for coordonnees in self.noeuds.values())
+        ax.set_xlim(min_x - 1, max_x + 1) 
+        ax.set_ylim(min_y - 1, max_y + 1)
+
+        ax.set_xlabel('X')
+        ax.set_ylabel('Y')
+        ax.set_title('Graphe')
+        fig.savefig("graphe.png")
+        plt.close(fig)
+        
+    def afficher_graphe(self):
+        print("Les stations :")
+        for identifiant, coordonnees in self.noeuds.items():
+            print(f"Identifiant : {identifiant}, Coordonnées : ({coordonnees['x']}, {coordonnees['y']})")
+
+        print("\nLes lignes :")
+        for ligne in self.lignes:
+            print(f"Fréquence : {ligne['freq']} min, Début : {ligne['start_time']} min, Fin : {ligne['end_time']} min, Stations : {ligne['noeuds']}")
+
+        if self.depot_node is not None:
+            print(f"\nNoeud de dépôt : {self.depot_node}")
+            
\ No newline at end of file
diff --git a/src/utils/Python visualiser/main.py b/src/utils/Python visualiser/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c7a1323732645452d0f6edd449afd7ad6c8061b
--- /dev/null
+++ b/src/utils/Python visualiser/main.py	
@@ -0,0 +1,13 @@
+from graphe import Graphe
+from voiture import lire_fichier_routes, afficher_routes, tracer_routes, recuperer_identifiants_noeuds
+from final import afficher_routes_et_graphe
+
+def main():
+    graphe = Graphe()
+    
+    graphe.lire_fichier("graph.txt")
+    routes = lire_fichier_routes("saev_routes.out")
+
+    afficher_routes_et_graphe(graphe, routes)
+
+main()
diff --git a/src/utils/Python visualiser/voiture.py b/src/utils/Python visualiser/voiture.py
new file mode 100644
index 0000000000000000000000000000000000000000..786e644158a7a4499a1916ab8a3d043ceab9cd92
--- /dev/null
+++ b/src/utils/Python visualiser/voiture.py	
@@ -0,0 +1,78 @@
+import matplotlib.pyplot as plt
+import random
+import math
+import time
+
+from graphe import Graphe
+
+def couleur_aleatoire():
+    return (random.randint(0, 255) / 255, random.randint(0, 255) / 255, random.randint(0, 255) / 255)
+
+def lire_fichier_routes(chemin_fichier):
+    routes = []
+    with open(chemin_fichier, 'r') as fichier:
+        for ligne in fichier:
+            if ligne.startswith("#SAEV Route format"):
+                continue
+            points_passage = ligne.strip().split(';')
+            route = []
+            for point in points_passage:
+                contenu = point.strip('()').split(',')
+                identifiant_noeud = contenu[0]
+                identifiant_requete = contenu[1]
+                fenetre_temps_min = contenu[2]
+                fenetre_temps_max = contenu[3]
+                route.append((identifiant_noeud, identifiant_requete, fenetre_temps_min, fenetre_temps_max))
+            routes.append(route)
+    return routes
+
+def afficher_routes(routes):
+    for i in range(len(routes)):
+        route = routes[i]
+        for point in route:
+            identifiant_noeud = int(point[0]) + 1
+            fenetre_temps_min = point[2]
+            fenetre_temps_max = point[3]
+            print(f"Noeud {identifiant_noeud} Fenêtre de temps ({fenetre_temps_min}, {fenetre_temps_max})")
+        print()
+
+def tracer_routes(routes, graphe, chemin_fichier):
+    fig, ax = plt.subplots()
+    ax.axis('off')
+    for i in range(len(routes)):
+        route = routes[i]
+        for j in range(len(route)):    # j est un point de passage 
+            noeud, id_requete, tw_min, tw_max = route[j]
+
+            if id_requete:  # Utilisation d'une graine aléatoire spécifique pour les couleurs
+                random.seed(int(id_requete))
+            else:
+                random.seed(int(time.time()))  # Utilisation du timestamp actuel comme graine aléatoire
+
+            couleur_aleatoire = couleur_aleatoire()
+
+            noeud = int(noeud)
+            label = f"({tw_min},{tw_max})"
+            ax.scatter(j, i+1, color=couleur_aleatoire, s=10)
+            ax.text(j, i+1 - 0.2, label, ha='center', va='top', fontsize=8)
+            ax.text(j, i+1 + 0.1, f"NÅ“ud {noeud}", ha='center', va='bottom', fontsize=8)
+            
+            if j < len(route) - 1:
+                noeud_suivant = int(route[j + 1][0])   # point de passage suivant 
+                distance = graphe.get_distance(noeud+1, noeud_suivant+1)
+                milieu_x = (j + (j + 1)) / 2
+                milieu_y = (i + 1 + (i + 1)) / 2
+                ax.text(milieu_x, milieu_y - 0.1, f"{distance:.2f}", ha='center', va='center', fontsize=8)
+                ax.plot([j, j+1], [i+1, i+1], color='black')
+
+    fig.savefig(chemin_fichier)
+    plt.close(fig)
+    
+def recuperer_identifiants_noeuds(routes):
+    identifiants_noeuds = []
+    for route in routes:
+        noeuds_route=[]
+        for point in route:
+            noeuds_route.append(int(point[0]))
+        identifiants_noeuds.append(noeuds_route)
+    return identifiants_noeuds
diff --git a/src/utils/SearchAlgorithms.h b/src/utils/SearchAlgorithms.h
index 90214d26e45af0a0fff8f348da9cf16e3c5dbffb..a3c5b72f0902534e815055f694e2d48f197af3e3 100644
--- a/src/utils/SearchAlgorithms.h
+++ b/src/utils/SearchAlgorithms.h
@@ -7,18 +7,28 @@
 
 
 #include <vector>
+#include <algorithm>
 
 template <typename Comparable>
 class SearchAlgorithms {
 public:
+    /**
+     * Returns an iterator to the first item higher or equal than the comparable value given
+     * @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 an iterator to the first item higher or equal than the comparable value given
+     */
+    static std::vector<Comparable>::const_iterator findNextSortedValue(const std::vector<Comparable>& sortedVector, Comparable value) {
+        return std::ranges::lower_bound(sortedVector, value);
+    }
     /**
      * 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);
+    static size_t findNextSortedValueIdx(const std::vector<Comparable>& sortedVector, Comparable value) {
+        auto iterator = std::ranges::lower_bound(sortedVector, value);
         return std::distance(sortedVector.begin(), iterator);
     }
 
@@ -28,8 +38,8 @@ public:
      * @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);
+    static size_t vectorBinarySearch(const std::vector<Comparable>& sortedVector, Comparable value) {
+        auto iterator = std::ranges::lower_bound(sortedVector, value);
         if (iterator == sortedVector.end() || *iterator != value) {
             return -1;
         } else {
diff --git a/src/utils/Timing.h b/src/utils/Timing.h
new file mode 100644
index 0000000000000000000000000000000000000000..5e720689105b5f87e434f626f1f41b50fb4aa2ce
--- /dev/null
+++ b/src/utils/Timing.h
@@ -0,0 +1,24 @@
+//
+// Created by romain on 26/08/24.
+//
+
+#ifndef GREEDYALGORITHM_TIMING_H
+#define GREEDYALGORITHM_TIMING_H
+
+#include <chrono>
+#define TIMING
+
+#ifdef TIMING
+#define INIT_TIMER auto start = std::chrono::high_resolution_clock::now();
+#define START_TIMER  start = std::chrono::high_resolution_clock::now();
+#define STOP_TIMER(name)  std::cout << "RUNTIME of " << name << ": " << \
+    std::chrono::duration_cast<std::chrono::milliseconds>( \
+            std::chrono::high_resolution_clock::now()-start \
+    ).count() << " ms " << std::endl;
+#else
+#define INIT_TIMER
+#define START_TIMER
+#define STOP_TIMER(name)
+#endif
+
+#endif //GREEDYALGORITHM_TIMING_H
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 451d74dfa26120d1acbaca161d1fe7f2217a1644..d30630e1a3976a9bb6ff85971a10eaf8c8d79f0c 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -5,6 +5,11 @@ project(Google_tests)
 add_subdirectory(lib/googletest)
 include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
 
+#add_definitions(-DDEBUG_TRANSIT_PRECOMPUTE)
+#add_definitions(-DDEBUG_CONSTRAINT_PROPAGATION)
+#add_definitions(-DDEBUG_BEST_INSERTION_HEURISTIC)
+#add_definitions(-DDEBUG_MULTIMODAL_HEURISTIC)
+
 # 'Google_Tests_run' is the target name
 # 'test1.cpp test2.cpp' are source files with tests
 add_executable(Line_UT
@@ -17,7 +22,187 @@ add_executable(Line_UT
         ../src/instance/graph/Graph.h
         ../src/instance/graph/Line.cpp
         ../src/instance/graph/Line.h
+        ../src/utils/Globals.h
+)
+
+add_executable(Transit_Preprocess_DEB
+        src/TransitPreprocessDebug.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
+        ../src/routes/KeyPoint.cpp
+        ../src/routes/KeyPoint.h
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.cpp
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h
+        ../src/utils/Globals.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
 )
-add_executable(Transit_preprocess_UT src/TransitPreprocessUnitTest.cpp)
+
+
+add_executable(Constraint_Propag_DEB
+        src/ConstraintPropagationDebug.cpp
+        ../src/TimeWindow.cpp
+        ../src/TimeWindow.h
+        ../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
+        ../src/instance/requests/Request.cpp
+        ../src/instance/requests/Request.h
+        ../src/routes/vehicle/SAEVRoute.cpp
+        ../src/routes/vehicle/SAEVRoute.h
+        ../src/routes/vehicle/propagation/SAEVRouteChange.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChange.h
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.h
+        ../src/routes/vehicle/SAEVKeyPoint.cpp
+        ../src/routes/vehicle/SAEVKeyPoint.h
+        ../src/routes/KeyPoint.cpp
+        ../src/routes/KeyPoint.h
+        ../src/routes/requests/RequestKeyPoint.cpp
+        ../src/routes/requests/RequestKeyPoint.h
+        ../src/routes/requests/RequestRoute.cpp
+        ../src/routes/requests/RequestRoute.h
+        "../src/utils/Instance Generation/Requests/RequestsGenerator.cpp"
+        "../src/utils/Instance Generation/Requests/RequestsGenerator.h"
+        "../src/utils/Instance Generation/Requests/RequestGenerationParameters.h"
+        "../src/utils/Instance Generation/Graph/OSRMGraphGenerator.h"
+        ../src/utils/Constants.h
+        ../src/algorithm/ShortestPath/Vehicle/VehiclePathState.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPath.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
+        ../src/algorithm/ShortestPath/Vehicle/MatrixShortestPathContainer.h
+)
+
+add_executable(BestInsertionHeuristic_DEB
+        src/BestInsertionHeuristicDebug.cpp
+        ../src/TimeWindow.cpp
+        ../src/TimeWindow.h
+        ../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
+        ../src/instance/requests/Request.cpp
+        ../src/instance/requests/Request.h
+        ../src/routes/vehicle/SAEVRoute.cpp
+        ../src/routes/vehicle/SAEVRoute.h
+        ../src/routes/vehicle/propagation/SAEVRouteChange.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChange.h
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.h
+        ../src/routes/vehicle/SAEVKeyPoint.cpp
+        ../src/routes/vehicle/SAEVKeyPoint.h
+        ../src/routes/KeyPoint.cpp
+        ../src/routes/KeyPoint.h
+        ../src/routes/requests/RequestKeyPoint.cpp
+        ../src/routes/requests/RequestKeyPoint.h
+        ../src/routes/requests/RequestRoute.cpp
+        ../src/routes/requests/RequestRoute.h
+        ../src/routes/vehicle/BestRequestInsertion.h
+        ../src/routes/vehicle/BestInsertionQueue.h
+        ../src/algorithm/DARP/Heuristics/BestInsertionHeuristic.cpp
+        ../src/algorithm/DARP/Heuristics/BestInsertionHeuristic.h
+        ../src/utils/Constants.h
+)
+
+add_executable(GraphPreprocess_DEB
+        src/GraphPreprocessDebug.cpp
+        ../src/TimeWindow.cpp
+        ../src/TimeWindow.h
+        ../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
+        ../src/instance/requests/Request.cpp
+        ../src/instance/requests/Request.h
+        ../src/routes/vehicle/SAEVRoute.cpp
+        ../src/routes/vehicle/SAEVRoute.h
+        ../src/routes/vehicle/propagation/SAEVRouteChange.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChange.h
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.h
+        ../src/routes/vehicle/SAEVKeyPoint.cpp
+        ../src/routes/vehicle/SAEVKeyPoint.h
+        ../src/routes/KeyPoint.cpp
+        ../src/routes/KeyPoint.h
+        ../src/routes/requests/RequestKeyPoint.cpp
+        ../src/routes/requests/RequestKeyPoint.h
+        ../src/routes/requests/RequestRoute.cpp
+        ../src/routes/requests/RequestRoute.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
+        ../src/utils/Constants.h
+)
+
+add_executable(MultimodalInsertionHeuristic_DEB
+        ../src/TimeWindow.cpp
+        ../src/TimeWindow.h
+        ../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
+        ../src/instance/requests/Request.cpp
+        ../src/instance/requests/Request.h
+        ../src/routes/vehicle/SAEVRoute.cpp
+        ../src/routes/vehicle/SAEVRoute.h
+        ../src/routes/vehicle/propagation/SAEVRouteChange.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChange.h
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.cpp
+        ../src/routes/vehicle/propagation/SAEVRouteChangelist.h
+        ../src/routes/vehicle/SAEVKeyPoint.cpp
+        ../src/routes/vehicle/SAEVKeyPoint.h
+        ../src/routes/KeyPoint.cpp
+        ../src/routes/KeyPoint.h
+        ../src/routes/requests/RequestKeyPoint.cpp
+        ../src/routes/requests/RequestKeyPoint.h
+        ../src/routes/requests/RequestRoute.cpp
+        ../src/routes/requests/RequestRoute.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h
+        ../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.cpp
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.cpp
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.cpp
+        ../src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h
+        ../src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.cpp
+        ../src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h
+        ../src/algorithm/Multimodal/Heuristics/TransitAccess.h
+        ../src/algorithm/DARP/Heuristics/BestInsertionHeuristic.cpp
+        ../src/algorithm/DARP/Heuristics/BestInsertionHeuristic.h
+        ../src/utils/Constants.h
+        src/MultimodalInsertionHeuristicDebug.cpp
+        ../src/utils/Timing.h
+        "../src/utils/Instance Generation/Requests/RequestsGenerator.cpp"
+        "../src/utils/Instance Generation/Requests/RequestsGenerator.h"
+)
+
 target_link_libraries(Line_UT gtest gtest_main)
-target_link_libraries(Transit_preprocess_UT gtest gtest_main)
\ No newline at end of file
+target_link_libraries(Transit_Preprocess_DEB gtest gtest_main)
+target_link_libraries(Constraint_Propag_DEB gtest gtest_main)
+target_link_libraries(BestInsertionHeuristic_DEB gtest gtest_main)
+target_link_libraries(GraphPreprocess_DEB gtest gtest_main)
+target_link_libraries(MultimodalInsertionHeuristic_DEB gtest gtest_main)
\ No newline at end of file
diff --git a/test/debug.cpp b/test/debug.cpp
deleted file mode 100644
index 2b7f45f471c95fa0c78367d581bb9dd7f7dc9558..0000000000000000000000000000000000000000
--- a/test/debug.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-//
-// 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
index eff443c6ef5eb6ab598bfaae27f9427fdb4f6af7..0953a17a4281fc26831da647ad3fcd5e21e6473b 160000
--- a/test/lib/googletest
+++ b/test/lib/googletest
@@ -1 +1 @@
-Subproject commit eff443c6ef5eb6ab598bfaae27f9427fdb4f6af7
+Subproject commit 0953a17a4281fc26831da647ad3fcd5e21e6473b
diff --git a/test/src/BestInsertionHeuristicDebug.cpp b/test/src/BestInsertionHeuristicDebug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..63e98a7717716a1a150dea62736f19eec7ba7552
--- /dev/null
+++ b/test/src/BestInsertionHeuristicDebug.cpp
@@ -0,0 +1,162 @@
+//
+// Created by romain on 20/03/24.
+//
+
+#include "../lib/googletest/googletest/include/gtest/gtest.h"
+#include "../../src/instance/graph/Graph.h"
+#include "../../src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h"
+#include "../../src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h"
+#include "../../src/instance/Instance.h"
+#include "../../src/routes/vehicle/SAEVRoute.h"
+#include "../../src/algorithm/DARP/Heuristics/BestInsertionHeuristic.h"
+
+TEST(BestInsertionHeuristicDebug, DebugBaseInstance) {
+    std::string instancesPath = "../../resources/test/instances/Constraint Propagation/";
+    std::string instanceFolder = "basic_debug_instance/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestsDatFile = "requests.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestsDatFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+
+    std::cout << "------------------Fin parsing instance et route-------------------" << std::endl << std::endl;
+    int vehicleId = 1;
+    assert(routesContainer.checkRouteTimeWindows(vehicleId));
+    SAEVRouteChangelist req0Changelist = routesContainer.tryAddRequest(0, &routesContainer.getOriginDepot(vehicleId), &routesContainer.getOriginDepot(1));
+    std::cout << routesContainer.to_string(vehicleId) << std::endl;
+    assert(routesContainer.checkRouteTimeWindows(vehicleId));
+    std::cout << "------------------------------------------------------------------" << std::endl;
+    SAEVRouteChangelist req1Changelist = routesContainer.tryAddRequest(1, &routesContainer.getOriginDepot(vehicleId),
+                                                                       &routesContainer.getRequestDestination(0));
+    std::cout << routesContainer.to_string(vehicleId) << std::endl << std::endl;
+    assert(!routesContainer.checkRouteTimeWindows(vehicleId));
+    std::cout << "------------------------------------------------------------------" << std::endl;
+
+    //Test changelist revert/apply
+    req1Changelist.revertChanges();
+    req1Changelist.applyChanges();
+    req1Changelist.revertChanges();
+    req0Changelist.revertChanges();
+}
+
+TEST(BestInsertionQueueDebug, DebugInstanceAlain) {
+    std::string instancesPath = "../../resources/test/instances/Constraint Propagation/";
+    std::string instanceFolder = "Instance_Alain_140624/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestsDatFile = "requests.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestsDatFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+
+    //Vehicle 1 insertions
+    BestInsertionQueue biQueue = routesContainer.getBestInsertionsQueue(0,0);
+    routesContainer.tryAddRequest(0,&routesContainer.getOriginDepot(0),&routesContainer.getOriginDepot(0));
+    routesContainer.getBestFeasibleInsertionsQueue(1,0);
+    routesContainer.tryAddRequest(1, &routesContainer.getRequestOrigin(0), &routesContainer.getRequestOrigin(0));
+    routesContainer.getBestFeasibleInsertionsQueue(2,0);
+    SAEVRouteChangelist cl = routesContainer.tryAddRequest(2, &routesContainer.getRequestOrigin(1),
+                                                           &routesContainer.getRequestDestination(1));
+    routesContainer.getBestFeasibleInsertionsQueue(3,0);
+
+    //Vehicle 2 insertions
+    routesContainer.getBestFeasibleInsertionsQueue(5,1);
+    routesContainer.tryAddRequest(5,&routesContainer.getOriginDepot(1),&routesContainer.getOriginDepot(1));
+    routesContainer.getBestFeasibleInsertionsQueue(4,1);
+    routesContainer.tryAddRequest(4,&routesContainer.getOriginDepot(1), &routesContainer.getRequestDestination(5));
+    routesContainer.getBestFeasibleInsertionsQueue(3,1);
+    routesContainer.tryAddRequest(3,&routesContainer.getOriginDepot(1), &routesContainer.getRequestOrigin(4));
+    routesContainer.getBestFeasibleInsertionsQueue(0,1);
+    routesContainer.getBestFeasibleInsertionsQueue(1,1);
+    routesContainer.getBestFeasibleInsertionsQueue(2,1);
+}
+
+TEST(BestInsertionPerVehicleHeuristicDebug, DebugInstanceAlain) {
+    std::string instancesPath = "../../resources/test/instances/Constraint Propagation/";
+    std::string instanceFolder = "Instance_Alain_140624/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestsDatFile = "requests.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestsDatFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+
+    //Vehicle 0 insertions (0,1,2)
+    BestInsertionHeuristic::tryVehicleBestInsertion(0, 0, routesContainer);
+    BestInsertionHeuristic::tryVehicleBestInsertion(1, 0, routesContainer);
+    BestInsertionHeuristic::tryVehicleBestInsertion(2, 0, routesContainer);
+    //Vehicle 1 insertions (3,4,5)
+    BestInsertionHeuristic::tryVehicleBestInsertion(3, 1, routesContainer);
+    BestInsertionHeuristic::tryVehicleBestInsertion(4, 1, routesContainer);
+    BestInsertionHeuristic::tryVehicleBestInsertion(5, 1, routesContainer);
+}
+
+TEST(BestInsertionHeuristicDebug, DebugInstanceAlain2) {
+    std::string instancesPath = "../../resources/test/instances/Constraint Propagation/";
+    std::string instanceFolder = "Instance_Alain_140624_2/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestsDatFile = "requests.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestsDatFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+
+    auto t0 = std::chrono::steady_clock::now();
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(0, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(1, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(2, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(3, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(4, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(5, routesContainer);
+    auto t1 = std::chrono::steady_clock::now();
+    auto d = t1 - t0;
+    std::cout << d.count() << "\n";
+}
+
+TEST(BestInsertionHeuristicDebug, DebugInstanceAlain) {
+    std::string instancesPath = "../../resources/test/instances/BestInsertionHeuristic/";
+    std::string instanceFolder = "DebugWeight/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestsDatFile = "requests.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestsDatFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+
+    auto t0 = std::chrono::steady_clock::now();
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(0, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(1, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(2, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(3, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(4, routesContainer);
+    BestInsertionHeuristic::doBestRequestInsertionForRoute(5, routesContainer);
+    auto t1 = std::chrono::steady_clock::now();
+    auto d = t1 - t0;
+    std::cout << d.count() << "\n";
+}
+
+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/ConstraintPropagationDebug.cpp b/test/src/ConstraintPropagationDebug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..70a3b9f70f5d80a1645851d471eca804e103f69f
--- /dev/null
+++ b/test/src/ConstraintPropagationDebug.cpp
@@ -0,0 +1,102 @@
+//
+// Created by romain on 20/03/24.
+//
+
+#include "../lib/googletest/googletest/include/gtest/gtest.h"
+#include "../../src/instance/graph/Graph.h"
+#include "../../src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h"
+#include "../../src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h"
+#include "../../src/instance/Instance.h"
+#include "../../src/routes/vehicle/SAEVRoute.h"
+#include "../../src/utils/Instance Generation/Requests/RequestsGenerator.h"
+#include "../../src/algorithm/ShortestPath/Vehicle/VehicleShortestPathCalculation.h"
+
+TEST(ConstraintPropagationDebug, DebugBaseInstance) {
+    std::string instancesPath = "../../resources/test/instances/Constraint Propagation/";
+    std::string instanceFolder = "basic_debug_instance/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestsDatFile = "requests.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestsDatFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+
+    std::cout << "------------------Fin parsing instance et route-------------------" << std::endl << std::endl;
+    int vehicleId = 1;
+    assert(routesContainer.checkRouteTimeWindows(vehicleId));
+    SAEVRouteChangelist req0Changelist = routesContainer.tryAddRequest(0, &routesContainer.getOriginDepot(vehicleId), &routesContainer.getOriginDepot(1));
+    std::cout << routesContainer.to_string(vehicleId) << std::endl;
+    assert(routesContainer.checkRouteTimeWindows(vehicleId));
+    std::cout << "------------------------------------------------------------------" << std::endl;
+    SAEVRouteChangelist req1Changelist = routesContainer.tryAddRequest(1, &routesContainer.getOriginDepot(vehicleId),
+                                                                       &routesContainer.getRequestDestination(0));
+    std::cout << routesContainer.to_string(vehicleId) << std::endl << std::endl;
+    assert(!routesContainer.checkRouteTimeWindows(vehicleId));
+    std::cout << "------------------------------------------------------------------" << std::endl;
+
+    //Test changelist revert/apply
+    req1Changelist.revertChanges();
+    req1Changelist.applyChanges();
+    req1Changelist.revertChanges();
+    req0Changelist.revertChanges();
+}
+
+TEST(ConstraintPropagationDebug, DebugRequestGeneration) {
+    std::string instancesPath = "../../resources/test/instances/Constraint Propagation/";
+    std::string instanceFolder = "basic_debug_instance/";
+    std::string graphDatFile = "graph.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = RequestsGenerator::generateAndExportRequests("../../resources/test/outputs/propag/" + instanceFolder, graphFromSingleFile,
+                                                                                 RequestGenerationParameters(100, 110620241720));
+    std::vector<Request> requestsParameterized = RequestsGenerator::generateAndExportRequests("../../resources/test/outputs/propag/" + instanceFolder,graphFromSingleFile,
+                                                                                              RequestGenerationParameters(100, 1.5, 15, 15, 480, 600, 110620241739));
+
+    assert(requests.size() == 100);
+    assert(requestsParameterized.size() == 100);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+}
+
+
+TEST(ConstraintPropagationDebug, DebugInstanceAlain) {
+    std::string instancesPath = "../../resources/test/instances/Constraint Propagation/";
+    std::string instanceFolder = "Instance_Alain_140624/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestsDatFile = "requests.dat";
+
+    //Parse graph
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestsDatFile, graphFromSingleFile);
+    VehicleShortestPathCalculation::computeAndUpdateShortestPathsForGraph(graphFromSingleFile, false);
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests);
+
+    //Vehicle 1 insertions
+    routesContainer.tryAddRequest(0,&routesContainer.getOriginDepot(0),&routesContainer.getOriginDepot(0));
+    routesContainer.tryAddRequest(1, &routesContainer.getRequestOrigin(0), &routesContainer.getRequestOrigin(0));
+    SAEVRouteChangelist clFailPrecondition = routesContainer.tryAddRequest(2, &routesContainer.getRequestOrigin(1),
+                                                           &routesContainer.getRequestDestination(1));
+    SAEVRouteChangelist clSuccess = routesContainer.tryAddRequest(2, &routesContainer.getRequestOrigin(1),
+                                                           &routesContainer.getRequestDestination(0));
+
+    //Vehicle 2 insertions
+    routesContainer.tryAddRequest(5,&routesContainer.getOriginDepot(1),&routesContainer.getOriginDepot(1));
+    routesContainer.tryAddRequest(4,&routesContainer.getOriginDepot(1), &routesContainer.getRequestDestination(5));
+    routesContainer.tryAddRequest(3,&routesContainer.getOriginDepot(1), &routesContainer.getRequestOrigin(4));
+}
+
+}
+
+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/GraphPreprocessDebug.cpp b/test/src/GraphPreprocessDebug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f47e62d8870a7350a4dd0be7ac297238479dc55a
--- /dev/null
+++ b/test/src/GraphPreprocessDebug.cpp
@@ -0,0 +1,20 @@
+//
+// Created by romain on 24/07/24.
+//
+
+#include "../lib/googletest/googletest/include/gtest/gtest.h"
+#include "../../src/ShortestPath/Vehicle/VehicleShortestPathCalculation.h"
+
+
+TEST(GraphPreprocessDebug, DebugBookInstance) {
+    std::string instancesPath = "../../resources/test/instances/graph preprocess/";
+    std::string instanceFolder = "book example/";
+    std::string graphDatFile = "graph.dat";
+
+    //Parse graph
+    Graph graph(instancesPath + instanceFolder + graphDatFile);
+    VehicleShortestPathCalculation::computeAndUpdateShortestPathsForGraph(graph, false);
+
+    std::string exportFolder = "../../resources/test/outputs/preprocess_debug/";
+    graph.exportGraphToFile(exportFolder + instanceFolder);
+}
diff --git a/test/src/LineUnitTests.cpp b/test/src/LineUnitTests.cpp
index bc8b3c43d45e7b6110af5f370d319f286779d995..04f90f07294a2277caae35162f85f71949cf57f3 100644
--- a/test/src/LineUnitTests.cpp
+++ b/test/src/LineUnitTests.cpp
@@ -18,7 +18,7 @@ TEST(LineTests, LineGenerationScheduleOrder) {
     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_GT(schedule[i], schedule[i + 1]); //assert schedule value order
             }
         }
         ASSERT_TRUE(line.checkSchedules()); //assert line schedule check function is coherent with our preceding assertion
diff --git a/test/src/MultimodalInsertionHeuristicDebug.cpp b/test/src/MultimodalInsertionHeuristicDebug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3d98bbd26623e06907cf8faf3c777fb227f7187c
--- /dev/null
+++ b/test/src/MultimodalInsertionHeuristicDebug.cpp
@@ -0,0 +1,194 @@
+//
+// Created by romain on 20/03/24.
+//
+
+#include "../lib/googletest/googletest/include/gtest/gtest.h"
+#include "../../src/instance/graph/Graph.h"
+#include "../../src/instance/Instance.h"
+#include "../../src/routes/vehicle/SAEVRoute.h"
+#include "../../src/utils/Timing.h"
+#include "../../src/algorithm/Multimodal/Heuristics/MultimodalModularHeuristic.h"
+#include "../../src/utils/Instance Generation/Requests/RequestsGenerator.h"
+
+TEST(MultimodalInsertionHeuristicDebug, DebugBasicInstance) {
+    std::string instancesPath = "../../resources/test/instances/MultimodalHeuristic/";
+    std::string instanceName = "basic debug multimodal/";
+    std::string graphFile = "graph.dat";
+    std::string requestsFile = "requests.dat";
+
+
+    //Parse graph
+    INIT_TIMER
+    Graph graphFromSingleFile(instancesPath + instanceName + graphFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceName + requestsFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests, true);
+    MultimodalModularHeuristic multimodalHeuristic(&graphFromSingleFile, &routesContainer, &requests);
+    STOP_TIMER("Instance parsing and init")
+    std::cout << "------------------Fin parsing instance et route-------------------" << std::endl << std::endl;
+
+    std::cout << "------------------Start preprocessings-------------------" << std::endl << std::endl;
+    START_TIMER
+    graphFromSingleFile.computeAndUpdateShortestTransitPaths();
+    graphFromSingleFile.computeAndUpdateShortestPathsMatrix(true);
+    STOP_TIMER("Preprocess")
+    std::cout << "------------------End preprocessings-------------------" << std::endl << std::endl;
+    std::cout << "------------------Start multimodal insertion-------------------" << std::endl << std::endl;
+    START_TIMER
+    multimodalHeuristic.doMultimodalRequestsInsertion();
+    STOP_TIMER("Multimodal insertion")
+    std::cout << "------------------End multimodal insertion-------------------" << std::endl << std::endl;
+}
+
+TEST(MultimodalInsertionHeuristicDebug, DebugUnfulfilledEntry) {
+    std::string instancesPath = "../../resources/test/instances/MultimodalHeuristic/";
+    std::string instanceName = "debug unfulfilled entry/";
+    std::string graphFile = "graph.dat";
+    std::string requestsFile = "requests.dat";
+
+
+    //Parse graph
+    INIT_TIMER
+    Graph graphFromSingleFile(instancesPath + instanceName + graphFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceName + requestsFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests, true);
+    MultimodalModularHeuristic multimodalHeuristic(&graphFromSingleFile, &routesContainer, &requests);
+    STOP_TIMER("Instance parsing and init")
+    std::cout << "------------------Fin parsing instance et route-------------------" << std::endl << std::endl;
+
+    std::cout << "------------------Start preprocessings-------------------" << std::endl << std::endl;
+    START_TIMER
+    graphFromSingleFile.computeAndUpdateShortestTransitPaths();
+    graphFromSingleFile.computeAndUpdateShortestPathsMatrix(true);
+    STOP_TIMER("Preprocess")
+    std::cout << "------------------End preprocessings-------------------" << std::endl << std::endl;
+    std::cout << "------------------Start multimodal insertion-------------------" << std::endl << std::endl;
+    START_TIMER
+    multimodalHeuristic.doMultimodalRequestsInsertion();
+    STOP_TIMER("Multimodal insertion")
+    std::cout << "------------------End multimodal insertion-------------------" << std::endl << std::endl;
+}
+
+TEST(MultimodalInsertionHeuristicDebug, DebugUnfulfilledExit) {
+    std::string instancesPath = "../../resources/test/instances/MultimodalHeuristic/";
+    std::string instanceName = "debug unfulfilled exit/";
+    std::string graphFile = "graph.dat";
+    std::string requestsFile = "requests.dat";
+
+
+    //Parse graph
+    INIT_TIMER
+    Graph graphFromSingleFile(instancesPath + instanceName + graphFile);
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceName + requestsFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests, true);
+    MultimodalModularHeuristic multimodalHeuristic(&graphFromSingleFile, &routesContainer, &requests);
+    STOP_TIMER("Instance parsing and init")
+    std::cout << "------------------Fin parsing instance et route-------------------" << std::endl << std::endl;
+
+    std::cout << "------------------Start preprocessings-------------------" << std::endl << std::endl;
+    START_TIMER
+    graphFromSingleFile.computeAndUpdateShortestTransitPaths();
+    graphFromSingleFile.computeAndUpdateShortestPathsMatrix(true);
+    STOP_TIMER("Preprocess")
+    std::cout << "------------------End preprocessings-------------------" << std::endl << std::endl;
+    std::cout << "------------------Start multimodal insertion-------------------" << std::endl << std::endl;
+    START_TIMER
+    multimodalHeuristic.doMultimodalRequestsInsertion();
+    STOP_TIMER("Multimodal insertion")
+    std::cout << "------------------End multimodal insertion-------------------" << std::endl << std::endl;
+}
+
+TEST(MultimodalInsertionHeuristicDebug, DebugPTInstance) {
+    std::string instancesPath = "../../resources/test/instances/PT Shortest Path/";
+    std::string instanceFolder = "contiguous_lines_debug_instance/";
+    std::string datFile = "graph.dat";
+
+
+    //Parse graph
+    INIT_TIMER
+    Graph graphFromSingleFile(instancesPath + instanceFolder + datFile);
+    graphFromSingleFile.computeAndUpdateShortestPathsMatrix(true); //compute shortest paths before generating requests since we need shortest paths
+    RequestGenerationParameters genParams(1, 1.5, 15,30,300,600,290820241032L);
+    std::vector<Request> requests = RequestsGenerator::generateRequests(graphFromSingleFile, genParams);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests, true);
+    MultimodalModularHeuristic multimodalHeuristic(&graphFromSingleFile, &routesContainer, &requests);
+    STOP_TIMER("Instance parsing and init")
+    std::cout << "------------------Fin parsing instance et route-------------------" << std::endl << std::endl;
+
+    std::cout << "------------------Start preprocessings-------------------" << std::endl << std::endl;
+    START_TIMER
+    graphFromSingleFile.computeAndUpdateShortestTransitPaths();
+    STOP_TIMER("Preprocess")
+    std::cout << "------------------End preprocessings-------------------" << std::endl << std::endl;
+    std::cout << "------------------Start multimodal insertion (entry)-------------------" << std::endl << std::endl;
+    START_TIMER
+    for(size_t i = 0; i < multimodalHeuristic.getNbBaseRequests(); ++i) {
+        multimodalHeuristic.insertBestTransitEntryInRoute(requests[i], i);
+    }
+    STOP_TIMER("Multimodal insertion (entry)")
+    std::cout << "------------------End multimodal insertion (entry)-------------------" << std::endl << std::endl;
+    std::cout << "------------------Start multimodal insertion (exit)-------------------" << std::endl << std::endl;
+    START_TIMER
+    for(size_t i = 0; i < multimodalHeuristic.getNbBaseRequests(); ++i) {
+        multimodalHeuristic.insertBestTransitExitsInRoute(requests[i], i);
+    }
+    STOP_TIMER("Multimodal insertion (exit)")
+    std::cout << "------------------End multimodal insertion (exit)-------------------" << std::endl << std::endl;
+}
+
+TEST(MultimodalInsertionHeuristicDebug, DebugInstanceAlain) {
+    std::string instancesPath = "../../resources/test/instances/MultimodalHeuristic/";
+    std::string instanceFolder = "instance_alain_140624/";
+    std::string graphDatFile = "graph.dat";
+    std::string requestDatFile = "even_more_requests.dat";
+
+
+    //Parse graph
+    INIT_TIMER
+    Graph graphFromSingleFile(instancesPath + instanceFolder + graphDatFile);
+    graphFromSingleFile.computeAndUpdateShortestPathsMatrix(true);  //compute shortest paths before generating requests since we need shortest paths
+    std::vector<Request> requests = Request::getRequestsFromFile(instancesPath + instanceFolder + requestDatFile, graphFromSingleFile);
+
+    //Init instance
+    Instance instance(requests,graphFromSingleFile,4);
+    SAEVRoute routesContainer(graphFromSingleFile, requests, true);
+    MultimodalModularHeuristic multimodalHeuristic(&graphFromSingleFile, &routesContainer, &requests);
+    STOP_TIMER("Instance parsing and init")
+    std::cout << "------------------Fin parsing instance et route-------------------" << std::endl << std::endl;
+
+    std::cout << "------------------Start preprocessings-------------------" << std::endl << std::endl;
+    START_TIMER
+    graphFromSingleFile.computeAndUpdateShortestTransitPaths();
+    STOP_TIMER("Preprocess")
+    std::cout << "------------------End preprocessings-------------------" << std::endl << std::endl;
+    std::cout << "------------------Start multimodal insertion (entry)-------------------" << std::endl << std::endl;
+    START_TIMER
+    for(size_t i = 0; i < multimodalHeuristic.getNbBaseRequests(); ++i) {
+        multimodalHeuristic.insertBestTransitEntryInRoute(requests[i], i);
+    }
+    STOP_TIMER("Multimodal insertion (entry)")
+    std::cout << "------------------End multimodal insertion (entry)-------------------" << std::endl << std::endl;
+    std::cout << "------------------Start multimodal insertion (exit)-------------------" << std::endl << std::endl;
+    START_TIMER
+    for(size_t i = 0; i < multimodalHeuristic.getNbBaseRequests(); ++i) {
+        multimodalHeuristic.insertBestTransitExitsInRoute(requests[i], i);
+    }
+    STOP_TIMER("Multimodal insertion (exit)")
+    std::cout << "------------------End multimodal insertion (exit)-------------------" << std::endl << std::endl;
+}
+
+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/TransitPreprocessDebug.cpp b/test/src/TransitPreprocessDebug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e5d5bf3fec3af6114fe5bae53dc3cfa259a4744a
--- /dev/null
+++ b/test/src/TransitPreprocessDebug.cpp
@@ -0,0 +1,65 @@
+//
+// Created by romain on 20/03/24.
+//
+
+#include "../lib/googletest/googletest/include/gtest/gtest.h"
+#include "../../src/instance/graph/Graph.h"
+#include "../../src/algorithm/ShortestPath/Transit/TransitShortestPathContainer.h"
+#include "../../src/algorithm/ShortestPath/Transit/TransitShortestPathPrecompute.h"
+
+TEST(Transit_Preprocess_DEB, DebugFunction) {
+    std::string instancesPath = "../../resources/test/instances/PT Shortest Path/";
+    std::string instanceFolder = "contiguous_lines_debug_instance/";
+    std::string datFile = "graph.dat";
+
+    Graph graphFromSingleFile(instancesPath + instanceFolder + datFile);
+    graphFromSingleFile.exportGraphToFile("../../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(ptLine.getNode(i), startingTime, graphFromSingleFile.getNbNodes(),
+                        TransitShortestPathPrecompute::executeAlgorithm(graphFromSingleFile, ptLine.getNode(i),startingTime));
+            }
+        }
+    }
+
+    TransitShortestPathContainer crossingContainer(graphFromSingleFile.getNbNodes());
+    Graph crossingLinesGraph(instancesPath + "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(ptLine.getNode(i), startingTime, crossingLinesGraph.getNbNodes(),
+                        TransitShortestPathPrecompute::executeAlgorithm(crossingLinesGraph, ptLine.getNode(i),startingTime));
+            }
+        }
+    }
+
+    TransitShortestPathContainer cycleContainer(graphFromSingleFile.getNbNodes());
+    Graph cyclingLineGraph(instancesPath + "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(ptLine.getNode(i), startingTime, cyclingLineGraph.getNbNodes(),
+                        TransitShortestPathPrecompute::executeAlgorithm(cyclingLineGraph, ptLine.getNode(i),startingTime));
+            }
+        }
+    }
+
+
+    TransitShortestPathContainer multiCycleContainer(graphFromSingleFile.getNbNodes());
+    Graph multipleCyclingLinesGraph(instancesPath + "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(ptLine.getNode(i), startingTime, multipleCyclingLinesGraph.getNbNodes(),
+                        TransitShortestPathPrecompute::executeAlgorithm(multipleCyclingLinesGraph, ptLine.getNode(i),startingTime));
+            }
+        }
+    }
+}
+
+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
deleted file mode 100644
index e2111003004bf6a78a09e73b1cd2495119610922..0000000000000000000000000000000000000000
--- a/test/src/TransitPreprocessUnitTest.cpp
+++ /dev/null
@@ -1,3 +0,0 @@
-//
-// Created by romain on 20/03/24.
-//