Skip to content
Snippets Groups Projects
Commit b758be1b authored by awenjb's avatar awenjb
Browse files

add output and simple visualisation

- add simple solution exporter (to json)
- add jupyter visualisation (map only)
parent 32dda218
No related branches found
No related tags found
No related merge requests found
...@@ -34,6 +34,7 @@ add_executable(pdptw src/mains/main.cpp ...@@ -34,6 +34,7 @@ add_executable(pdptw src/mains/main.cpp
src/lns/modification/route/insert_route.cpp src/lns/modification/route/insert_route.cpp
src/lns/modification/route/remove_route.cpp src/lns/modification/route/remove_route.cpp
src/output/solution_checker.cpp src/output/solution_checker.cpp
src/output/solution_exporter.cpp
src/lns/operators/sorting_strategy.cpp src/lns/operators/sorting_strategy.cpp
src/lns/operators/destruction/random_destroy.cpp src/lns/operators/destruction/random_destroy.cpp
src/lns/operators/generators/enumerate.cpp src/lns/operators/generators/enumerate.cpp
......
import json
import datetime
import enum
import folium
import numpy as np
import pandas as pd
import osmnx as ox
import networkx as nx
def read_json(file):
try:
with open(file, 'r') as file:
data = json.load(file)
return data
except FileNotFoundError:
print(f"Error : The file {file} does not exist.")
except json.JSONDecodeError:
print(f"Error : The file {file} is not a valid JSON file.")
except Exception as e:
print(f"Error : {e}")
G_drive = ox.graph_from_point((47.213811, -1.553168), dist=10000,
network_type='bike')
class PDPTWSolution:
def __init__(self, filename: str):
with open(filename, mode="r", encoding="utf-8") as f:
self._json_file = json.load(f)
self._parse_json()
def _parse_json(self):
if self._json_file is None:
raise "Trying to parse json, but was not loaded"
self._routes: list[dict] = self._json_file["routes"]
def get_routes(self):
return self._routes
def get_json(self):
return self._json_file
class PDPTWData:
def __init__(self, filename: str):
with open(filename, mode="r", encoding="utf-8") as f:
self._json_file = json.load(f)
self._parse_json()
def _parse_json(self):
if self._json_file is None:
raise "Trying to parse json, but was not loaded"
self._locations: list[dict] = self._json_file["locations"]
self._depot: list[dict] = self._json_file["depot"]
self._capacity = self._json_file["capacity"]
def get_depot(self):
return self._depot
def get_locations(self):
return self._locations
def get_capacity(self):
return self._capacity
def get_json(self):
return self._json_file
def get_route_as_dataframe(data: PDPTWData, solution: PDPTWSolution, route_index:int) -> pd.DataFrame:
route_df=pd.DataFrame()
route_ids = solution.get_routes()[route_index]["locationIDs"]
for id in route_ids:
line = {"route": route_index,
"location": id,
"type": data.get_locations()[id - 1]["locType"],
"latitude": data.get_locations()[id - 1]["latitude"],
"longitude": data.get_locations()[id - 1]["longitude"]}
route_df = pd.concat([route_df, pd.DataFrame.from_dict([line])], ignore_index=True)
return route_df
def display_points_of_interest(map: folium.Map, data: PDPTWData):
folium.Marker(location=[data.get_depot()["latitude"], data.get_depot()["longitude"]],
icon=folium.Icon(color="red", popup="DEPOT")).add_to(map)
for location in data.get_locations():
if location["locType"] == "PICKUP":
color = "darkblue"
text = "pickup"
elif location["locType"] == "DELIVERY":
color = "lightblue"
text = "delivery"
else:
continue
folium.Marker(location=[location["latitude"], location["longitude"]],
icon=folium.Icon(color=color, popup=text)).add_to(map)
def display_route(map: folium.Map, data: PDPTWData, solution: PDPTWSolution, route_index: int, color: str="red"):
df = get_route_as_dataframe(data, solution, route_index)
# add depot at the begining and the end
types = ["DEPOT"] + list(df["type"]) + ["DEPOT"]
depot_coord = (data.get_depot()["latitude"], data.get_depot()["longitude"])
locations = [depot_coord] + list(zip(df["latitude"], df["longitude"])) + [depot_coord]
real_path_locations = []
network_locations = ox.nearest_nodes(G_drive,
[data.get_depot()["longitude"]] + list(df["longitude"]) + [data.get_depot()["longitude"]],
[data.get_depot()["latitude"]] + list(df["latitude"]) + [data.get_depot()["latitude"]])
for i in range(1, len(locations)):
edge = [network_locations[i - 1], network_locations[i]]
real_nodes = ox.shortest_path(G_drive, edge[0], edge[1], weight="length")
if real_nodes is not None:
long_lat_edges = []
for node in real_nodes:
g_drive_node = G_drive.nodes[node]
if "x" in g_drive_node and "y" in g_drive_node:
long_lat_edges.append((g_drive_node["y"], g_drive_node["x"]))
real_path_locations.extend(long_lat_edges)
else:
real_path_locations.append(locations[i])
folium.PolyLine(real_path_locations, color=color, weight=5, opacity=0.5, smooth_factor=0).add_to(map)
for index, row in df.iterrows():
if row["type"] == "REQUEST":
text = f'{index} - {row["latitude"], row["longitude"]} : times ({row["arrival"]},{row["service"]},{row["departure"]}), time window [{row["Hmin"]},{row["Hmax"]}]'
folium.Circle(location=(row["latitude"], row["longitude"]), fill_color="orange", radius=4,
tooltip=text).add_to(map)
%% Cell type:code id: tags:
``` python
%pip install folium pandas OSMnx networkx scikit-learn
```
%% Output
Requirement already satisfied: folium in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (0.19.4)
Requirement already satisfied: pandas in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (2.2.3)
Requirement already satisfied: OSMnx in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (2.0.1)
Requirement already satisfied: networkx in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (3.4.2)
Requirement already satisfied: scikit-learn in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (1.6.1)
Requirement already satisfied: branca>=0.6.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (0.8.1)
Requirement already satisfied: jinja2>=2.9 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (3.1.5)
Requirement already satisfied: numpy in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (2.2.3)
Requirement already satisfied: requests in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (2.32.3)
Requirement already satisfied: xyzservices in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from folium) (2025.1.0)
Requirement already satisfied: python-dateutil>=2.8.2 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from pandas) (2025.1)
Requirement already satisfied: tzdata>=2022.7 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from pandas) (2025.1)
Requirement already satisfied: geopandas>=1.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from OSMnx) (1.0.1)
Requirement already satisfied: shapely>=2.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from OSMnx) (2.0.7)
Requirement already satisfied: scipy>=1.6.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from scikit-learn) (1.15.2)
Requirement already satisfied: joblib>=1.2.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from scikit-learn) (1.4.2)
Requirement already satisfied: threadpoolctl>=3.1.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from scikit-learn) (3.5.0)
Requirement already satisfied: pyogrio>=0.7.2 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from geopandas>=1.0->OSMnx) (0.10.0)
Requirement already satisfied: packaging in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from geopandas>=1.0->OSMnx) (24.2)
Requirement already satisfied: pyproj>=3.3.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from geopandas>=1.0->OSMnx) (3.7.1)
Requirement already satisfied: MarkupSafe>=2.0 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from jinja2>=2.9->folium) (3.0.2)
Requirement already satisfied: six>=1.5 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (3.4.1)
Requirement already satisfied: idna<4,>=2.5 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (2.3.0)
Requirement already satisfied: certifi>=2017.4.17 in /home/a24jacqb/.julia/conda/3/x86_64/lib/python3.12/site-packages (from requests->folium) (2025.1.31)
Note: you may need to restart the kernel to use updated packages.
%% Cell type:code id: tags:
``` python
import solution_reader
import datetime as dt
import folium
```
%% Cell type:code id: tags:
``` python
# Files
solutionFile = "./../output/Nantes_1_sol.json"
dataFile = "./../data_in/Nantes_1.json"
```
%% Cell type:code id: tags:
``` python
solution = solution_reader.PDPTWSolution(solutionFile)
data = solution_reader.PDPTWData(dataFile)
```
%% Cell type:markdown id: tags:
# Map
%% Cell type:code id: tags:
``` python
f = folium.Figure(width=1000, height=1000)
map = folium.Map(location=(47.213811, -1.553168), zoom_start=15)
solution_reader.display_points_of_interest(map, data)
solution_reader.display_route(map, data, solution, 0)
f.add_child(map)
f
```
%% Output
<branca.element.Figure at 0x7fadee233080>
...@@ -4,19 +4,18 @@ ...@@ -4,19 +4,18 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <utility>
// spdlog repris du code lns-framework mais pas utilisé ici -> TODO // spdlog repris du code lns-framework mais pas utilisé ici -> TODO
namespace fs = std::filesystem; namespace fs = std::filesystem;
using json = nlohmann::json; using json = nlohmann::json;
bool checkFilePresence(std::string &filepath) bool checkFilePresence(std::string &filepath)
{ {
return fs::is_regular_file(filepath); return fs::is_regular_file(filepath);
} }
PDPTWData parsing::parseJson(std::string filepath) PDPTWData parsing::parseJson(std::string filepath)
{ {
if (!checkFilePresence(filepath)) if (!checkFilePresence(filepath))
...@@ -24,6 +23,7 @@ PDPTWData parsing::parseJson(std::string filepath) ...@@ -24,6 +23,7 @@ PDPTWData parsing::parseJson(std::string filepath)
spdlog::error("Data file \"{}\" does not exist", filepath); spdlog::error("Data file \"{}\" does not exist", filepath);
exit(1); exit(1);
} }
std::ifstream jsonFile(filepath); std::ifstream jsonFile(filepath);
if (!jsonFile.is_open()) if (!jsonFile.is_open())
...@@ -35,11 +35,22 @@ PDPTWData parsing::parseJson(std::string filepath) ...@@ -35,11 +35,22 @@ PDPTWData parsing::parseJson(std::string filepath)
try try
{ {
// extract filename (data name)
size_t pos = filepath.find_last_of("/\\");
std::string filename = (pos != std::string::npos) ? filepath.substr(pos + 1) : filepath;
size_t dotPos = filename.find_last_of('.');
if (dotPos != std::string::npos)
{
filename = filename.substr(0, dotPos);
}
// generate PDPTWData
json j; json j;
jsonFile >> j; jsonFile >> j;
return json_to_data(j); return json_to_data(filename, j);
}
catch (std::exception const &e)// catching all exceptions to properly log the error and quit gracefully } catch (std::exception const &e)// catching all exceptions to properly log the error and quit gracefully
{ {
spdlog::error("Error while parsing the input json:"); spdlog::error("Error while parsing the input json:");
spdlog::error("{}", e.what()); spdlog::error("{}", e.what());
...@@ -48,46 +59,41 @@ PDPTWData parsing::parseJson(std::string filepath) ...@@ -48,46 +59,41 @@ PDPTWData parsing::parseJson(std::string filepath)
} }
} }
PDPTWData json_to_data(const json& j) PDPTWData json_to_data(std::string dataName, json const &j)
{ {
int size = j.at("size").get<int>(); int size = j.at("size").get<int>();
int capacity = j.at("capacity").get<int>(); int capacity = j.at("capacity").get<int>();
auto depot_json = j.at("depot"); auto depot_json = j.at("depot");
TimeWindow tw(depot_json.at("timeWindow").at(0), depot_json.at("timeWindow").at(1)); TimeWindow tw(depot_json.at("timeWindow").at(0), depot_json.at("timeWindow").at(1));
Location depot( Location depot(0,
0, depot_json.at("longitude"),
depot_json.at("longitude"), depot_json.at("latitude"),
depot_json.at("latitude"), 0,
0, tw,
tw, depot_json.at("serviceDuration"),
depot_json.at("serviceDuration"), 0,
0, LocType::DEPOT);
LocType::DEPOT
);
std::vector<Location> locations; std::vector<Location> locations;
for (const auto& loc : j.at("locations")) for (auto const &loc: j.at("locations"))
{ {
LocType loc_type = (loc.at("locType") == "PICKUP") ? LocType::PICKUP : LocType::DELIVERY; LocType loc_type = (loc.at("locType") == "PICKUP") ? LocType::PICKUP : LocType::DELIVERY;
TimeWindow loc_tw(loc.at("timeWindow").at(0), loc.at("timeWindow").at(1)); TimeWindow loc_tw(loc.at("timeWindow").at(0), loc.at("timeWindow").at(1));
locations.emplace_back( locations.emplace_back(loc.at("id"),
loc.at("id"), loc.at("longitude"),
loc.at("longitude"), loc.at("latitude"),
loc.at("latitude"), loc.at("demand"),
loc.at("demand"), loc_tw,
loc_tw, loc.at("serviceDuration"),
loc.at("serviceDuration"), loc.at("pairedLocation"),
loc.at("pairedLocation"), loc_type);
loc_type
);
} }
Matrix distance_matrix = j.at("distance_matrix").get<Matrix>(); Matrix distance_matrix = j.at("distance_matrix").get<Matrix>();
return {size, capacity, depot, locations, distance_matrix}; return {std::move(dataName), size, capacity, depot, locations, distance_matrix};
} }
...@@ -10,4 +10,4 @@ namespace parsing ...@@ -10,4 +10,4 @@ namespace parsing
PDPTWData parseJson(std::string filepath); PDPTWData parseJson(std::string filepath);
} }
PDPTWData json_to_data(const json& j); PDPTWData json_to_data(std::string dataName, const json& j);
\ No newline at end of file \ No newline at end of file
...@@ -30,6 +30,11 @@ Location const &PDPTWData::getDepot() const ...@@ -30,6 +30,11 @@ Location const &PDPTWData::getDepot() const
return depot; return depot;
} }
std::string PDPTWData::getDataName() const
{
return dataName;
}
Location const &PDPTWData::getLocation(int id) const Location const &PDPTWData::getLocation(int id) const
{ {
if (id==0) if (id==0)
...@@ -45,8 +50,8 @@ Matrix const &PDPTWData::getMatrix() const ...@@ -45,8 +50,8 @@ Matrix const &PDPTWData::getMatrix() const
return distanceMatrix; return distanceMatrix;
} }
PDPTWData::PDPTWData(int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix) PDPTWData::PDPTWData(std::string dataName, int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix)
: size(size), capacity(capacity), depot(depot), locations(std::move(locations)), distanceMatrix(std::move(distanceMatrix)) : dataName(dataName), size(size), capacity(capacity), depot(depot), locations(std::move(locations)), distanceMatrix(std::move(distanceMatrix))
{ {
// Associate pair of locations // Associate pair of locations
pairs.clear(); pairs.clear();
...@@ -75,6 +80,7 @@ const Pair &PDPTWData::getPair(int id) const ...@@ -75,6 +80,7 @@ const Pair &PDPTWData::getPair(int id) const
void PDPTWData::print() const void PDPTWData::print() const
{ {
std::cout << "Instance name : " << dataName << "\n";
std::cout << "Instance size: " << size << "\n"; std::cout << "Instance size: " << size << "\n";
std::cout << "Capacity: " << capacity << "\n"; std::cout << "Capacity: " << capacity << "\n";
std::cout << "Depot:\n"; std::cout << "Depot:\n";
......
...@@ -23,6 +23,7 @@ public: ...@@ -23,6 +23,7 @@ public:
class PDPTWData class PDPTWData
{ {
std::string dataName;
int size; int size;
int capacity; int capacity;
Location depot; Location depot;
...@@ -42,7 +43,7 @@ public: ...@@ -42,7 +43,7 @@ public:
* Constructs an empty PDPTWData. * Constructs an empty PDPTWData.
* @see parsing::parseJson * @see parsing::parseJson
*/ */
PDPTWData(int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix); PDPTWData(std::string dataName, int size, int capacity, Location depot, std::vector<Location> locations, Matrix distanceMatrix);
/** /**
* Checks some data coherence * Checks some data coherence
*/ */
...@@ -67,6 +68,6 @@ public: ...@@ -67,6 +68,6 @@ public:
int getSize() const; int getSize() const;
int getCapacity() const; int getCapacity() const;
std::string getDataName() const;
void print() const; void print() const;
}; };
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "lns/operators/generators/modification_generator.h" #include "lns/operators/generators/modification_generator.h"
#include "lns/operators/sorting_strategy.h" #include "lns/operators/sorting_strategy.h"
#include "output/solution_exporter.h"
using json = nlohmann::json; using json = nlohmann::json;
...@@ -82,5 +83,7 @@ int main(int argc, char const *argv[]) ...@@ -82,5 +83,7 @@ int main(int argc, char const *argv[])
operatorInstance.reconstructSolution(solution, blinkRate); operatorInstance.reconstructSolution(solution, blinkRate);
solution.print(); solution.print();
output::exportToJson(solution);
return 0; return 0;
} }
#include "solution_exporter.h"
std::string getCurrentDate()
{
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
char buffer[11];// "DD-MM-YYYY" + null terminator
std::strftime(buffer, sizeof(buffer), "%d-%m-%Y", &tm);
return std::string(buffer);
}
nlohmann::ordered_json output::getMinimalJson(Solution const &solution)
{
nlohmann::ordered_json jsonSolution;
nlohmann::ordered_json jsonRoutes = nlohmann::ordered_json::array();
int routeID = 0;
for (auto const &route: solution.getRoutes())
{
jsonRoutes.push_back(routeToJson(routeID, route));
++routeID;
}
jsonSolution["InstanceName"] = solution.getData().getDataName();
jsonSolution["Authors"] = "...";
jsonSolution["Date"] = getCurrentDate();
jsonSolution["Reference"] = "...";
jsonSolution["routes"] = jsonRoutes;
return jsonSolution;
}
nlohmann::ordered_json output::getCompleteJson(Solution const &solution)
{
nlohmann::ordered_json jsonSolution;
// TO DO
return jsonSolution;
}
void output::exportToJson(Solution const &solution)
{
std::string directory = "./../../output";
std::string filename = directory + "/" + solution.getData().getDataName() + "_sol.json";
if (!std::filesystem::exists(directory))
{
std::filesystem::create_directory(directory);
}
std::ofstream file(filename);
if (!file)
{
spdlog::error("Error when opening the file {}", filename);
exit(1);
return;
}
nlohmann::ordered_json jsonData = output::getMinimalJson(solution);
file << jsonData.dump();
file.close();
std::cout << "Solution exported" << std::endl;
}
nlohmann::ordered_json output::routeToJson(int routeID, Route const &route)
{
return nlohmann::ordered_json{{"routeID", routeID}, {"locationIDs", route.getRoute()}};
}
#pragma once
#include "lns/solution/solution.h"
#include <nlohmann/json.hpp>
#include <ctime>
#include <fstream>
namespace output
{
/**
* Get a json representation of a solution with the same info as in the benchmarks results (Li&Lim, Sartori&Buriol)
*/
nlohmann::ordered_json getMinimalJson(Solution const &solution);
/**
* Get a complete json representation of a solution (heavier)
*/
nlohmann::ordered_json getCompleteJson(Solution const &solution);
nlohmann::ordered_json routeToJson(int routeID, const Route& route);
void exportToJson(Solution const &solution);
}// namespace output
std::string getCurrentDate();
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment