Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/dsf/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ PYBIND11_MODULE(dsf_cpp, m) {
&dsf::mobility::RoadNetwork::importTrafficLights,
pybind11::arg("fileName"),
dsf::g_docstrings.at("dsf::mobility::RoadNetwork::importTrafficLights").c_str())
.def("setTransitionProbabilities",
&dsf::mobility::RoadNetwork::setTransitionProbabilities,
pybind11::arg("transitionProbabilities"),
dsf::g_docstrings.at("dsf::mobility::RoadNetwork::setTransitionProbabilities")
.c_str())
.def(
"makeRoundabout",
[](dsf::mobility::RoadNetwork& self, dsf::Id id) -> void {
Expand Down
5 changes: 5 additions & 0 deletions src/dsf/mobility/RoadDynamics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,14 @@
// Get current street information
std::optional<Id> previousNodeId = std::nullopt;
std::set<Id> forbiddenTurns;
std::unordered_map<Id, double> baseTransitionProbabilities;

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 12.3 rule Note

MISRA 12.3 rule
double speedCurrent = 1.0;
if (pAgent->streetId().has_value()) {
auto const& pStreetCurrent{this->graph().edge(pAgent->streetId().value())};
previousNodeId = pStreetCurrent->source();
forbiddenTurns = pStreetCurrent->forbiddenTurns();
speedCurrent = pStreetCurrent->maxSpeed();
baseTransitionProbabilities = pStreetCurrent->transitionProbabilities();
}
// Get path targets for non-random agents
Expand Down Expand Up @@ -551,6 +553,9 @@
// Calculate base probability
auto const speedNext{pStreetOut->maxSpeed()};
double probability = speedCurrent * speedNext;
if (!baseTransitionProbabilities.empty()) {
probability *= baseTransitionProbabilities.at(outEdgeId); // edge-specific prob
}
// Apply error probability for non-random agents
if (this->m_errorProbability.has_value() && !pathTargets.empty()) {
Expand Down
39 changes: 28 additions & 11 deletions src/dsf/mobility/RoadNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,17 @@
}
}
// Check for transition probabilities
// if (!edge_properties.at_key("transition_probabilities").error() &&
// edge_properties["transition_probabilities"].has_value()) {
// auto const& tp = edge_properties["transition_probabilities"];
// std::unordered_map<Id, double> transitionProbabilities;
// for (auto const& [key, value] : tp.get_object()) {
// auto const targetStreetId = static_cast<Id>(std::stoull(std::string(key)));
// auto const probability = static_cast<double>(value.get_double());
// transitionProbabilities.emplace(targetStreetId, probability);
// }
// edge(edge_id)->setTransitionProbabilities(transitionProbabilities);
// }
if (!edge_properties.at_key("transition_probabilities").error() &&
edge_properties["transition_probabilities"].has_value()) {
auto const& tp = edge_properties["transition_probabilities"];
std::unordered_map<Id, double> transitionProbabilities;

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 12.3 rule Note

MISRA 12.3 rule
for (auto const& [key, value] : tp.get_object()) {

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 12.3 rule Note

MISRA 12.3 rule
auto const targetStreetId = static_cast<Id>(std::stoull(std::string(key)));
auto const probability = static_cast<double>(value.get_double());
transitionProbabilities.emplace(targetStreetId, probability);
}
edge(edge_id)->setTransitionProbabilities(transitionProbabilities);
}
}
this->m_nodes.rehash(0);
this->m_edges.rehash(0);
Expand Down Expand Up @@ -792,6 +792,23 @@
}
}

void RoadNetwork::setTransitionProbabilities(
std::unordered_map<Id, std::unordered_map<Id, double>> const&
transitionProbabilities) {
std::for_each(DSF_EXECUTION m_edges.cbegin(),
m_edges.cend(),
[&transitionProbabilities](auto const& pair) {

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 13.1 rule Note

MISRA 13.1 rule
auto const& streetId = pair.first;
auto const& pStreet = pair.second;
auto const it = transitionProbabilities.find(streetId);
if (it != transitionProbabilities.end()) {
pStreet->setTransitionProbabilities(it->second);
} else {
pStreet->setTransitionProbabilities({});
}
});
}

TrafficLight& RoadNetwork::makeTrafficLight(Id const nodeId,
Delay const cycleTime,
Delay const counter) {
Expand Down
4 changes: 4 additions & 0 deletions src/dsf/mobility/RoadNetwork.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ namespace dsf::mobility {
/// and the speed limit, if such data is available in the file.
void importTrafficLights(const std::string& fileName);

void setTransitionProbabilities(
std::unordered_map<Id, std::unordered_map<Id, double>> const&
transitionProbabilities);

template <typename T1, typename... Tn>
requires is_node_v<std::remove_reference_t<T1>> &&
(is_node_v<std::remove_reference_t<Tn>> && ...)
Expand Down
26 changes: 22 additions & 4 deletions src/dsf/mobility/Street.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,28 @@
strLaneMapping +=
std::format("{} - ", directionToString[static_cast<size_t>(item)]);
});
spdlog::debug("New lane mapping for street {} -> {} is: {}",
m_nodePair.first,
m_nodePair.second,
strLaneMapping);
spdlog::debug("New lane mapping for {} is: {}", *this, strLaneMapping);
}
void Street::setTransitionProbabilities(
std::unordered_map<Id, double> const& transitionProbabilities) noexcept {
// Ensure normalization
if (transitionProbabilities.empty()) {
m_transitionProbabilities.clear();
return;

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 15.5 rule Note

MISRA 15.5 rule
}
double sumProbabilities{0.};
for (auto const& [_, probability] : transitionProbabilities) {

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 12.3 rule Note

MISRA 12.3 rule
sumProbabilities += probability;
}
if (std::abs(sumProbabilities - 1.) > 1e-6) {
auto tp = transitionProbabilities;
for (auto& [_, probability] : tp) {

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 12.3 rule Note

MISRA 12.3 rule
probability /= sumProbabilities;
}
m_transitionProbabilities = tp;
return;

Check notice

Code scanning / Cppcheck (reported by Codacy)

MISRA 15.5 rule Note

MISRA 15.5 rule
}
m_transitionProbabilities = transitionProbabilities;
}
void Street::setQueue(dsf::queue<std::unique_ptr<Agent>> queue, size_t index) {
assert(index < m_exitQueues.size());
Expand Down
21 changes: 10 additions & 11 deletions src/dsf/mobility/Street.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ namespace dsf::mobility {
AgentComparator>
m_movingAgents;
std::vector<Direction> m_laneMapping;
// std::unordered_map<Id, double> m_transitionProbabilities;
std::unordered_map<Id, double> m_transitionProbabilities;
std::optional<Counter> m_counter;

public:
Expand Down Expand Up @@ -84,12 +84,10 @@ namespace dsf::mobility {
/// @param meanVehicleLength The mean vehicle length
/// @throw std::invalid_argument If the mean vehicle length is negative
static void setMeanVehicleLength(double meanVehicleLength);
// /// @brief Set the street's transition probabilities
// /// @param transitionProbabilities The street's transition probabilities
// inline void setTransitionProbabilities(
// std::unordered_map<Id, double> const& transitionProbabilities) {
// m_transitionProbabilities = transitionProbabilities;
// };
/// @brief Set the street's transition probabilities
/// @param transitionProbabilities The street's transition probabilities
void setTransitionProbabilities(
std::unordered_map<Id, double> const& transitionProbabilities) noexcept;
/// @brief Enable a coil (dsf::Counter sensor) on the street
/// @param name The name of the counter (default is "Coil_<street_id>")
void enableCounter(std::string name = std::string());
Expand Down Expand Up @@ -117,10 +115,11 @@ namespace dsf::mobility {
/// @brief Check if the street is full
/// @return bool, True if the street is full, false otherwise
inline bool isFull() const final { return this->nAgents() == this->m_capacity; }

// inline auto const& transitionProbabilities() const {
// return m_transitionProbabilities;
// }
/// @brief Get the street's transition probabilities
/// @return std::unordered_map<Id, double> The street's transition probabilities
inline auto const& transitionProbabilities() const {
return m_transitionProbabilities;
}
/// @brief Get the name of the counter
/// @return std::string The name of the counter
inline auto counterName() const {
Expand Down
88 changes: 88 additions & 0 deletions test/data/test_transition_probs.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[11.8810, 44.2230],
[11.8820, 44.2240]
]
},
"properties": {
"id": 1,
"source": 0,
"target": 1,
"length": 100.5,
"maxspeed": 50,
"nlanes": 2,
"name": "Test Street A",
"transition_probabilities": {
"2": 0.6,
"3": 0.4
}
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[11.8820, 44.2240],
[11.8830, 44.2250]
]
},
"properties": {
"id": 2,
"source": 1,
"target": 2,
"length": 80.0,
"maxspeed": "30",
"nlanes": 1,
"name": "Test Street B"
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[11.8820, 44.2240],
[11.8815, 44.2260]
]
},
"properties": {
"id": 3,
"source": 1,
"target": 3,
"length": 120.0,
"maxspeed": 40,
"nlanes": 1,
"name": "Test Street C",
"transition_probabilities": {
"4": 1.0
}
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[11.8815, 44.2260],
[11.8810, 44.2270]
]
},
"properties": {
"id": 4,
"source": 3,
"target": 4,
"length": 90.0,
"maxspeed": 50,
"nlanes": 2,
"name": "Test Street D"
}
}
]
}
56 changes: 56 additions & 0 deletions test/mobility/Test_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -899,4 +899,60 @@ TEST_CASE("ShortestPath") {
CHECK_EQ(path.size(), 1);
CHECK_EQ(path[0], 0);
}

SUBCASE("Import GeoJSON with Transition Probabilities") {
GIVEN("A GeoJSON file with transition probabilities") {
RoadNetwork graph;

WHEN("We import the GeoJSON file") {
graph.importEdges((DATA_FOLDER / "test_transition_probs.geojson").string());

THEN("The graph is constructed correctly") {
CHECK_EQ(graph.nEdges(), 4);
CHECK_EQ(graph.nNodes(), 5);
}

THEN("Transition probabilities are correctly imported for street 1") {
auto const& street1 = graph.edge(1);
auto const& transProbs1 = street1->transitionProbabilities();

CHECK_EQ(transProbs1.size(), 2);
CHECK(transProbs1.contains(2));
CHECK(transProbs1.contains(3));
CHECK_EQ(transProbs1.at(2), doctest::Approx(0.6));
CHECK_EQ(transProbs1.at(3), doctest::Approx(0.4));
}

THEN("Transition probabilities are correctly imported for street 3") {
auto const& street3 = graph.edge(3);
auto const& transProbs3 = street3->transitionProbabilities();

CHECK_EQ(transProbs3.size(), 1);
CHECK(transProbs3.contains(4));
CHECK_EQ(transProbs3.at(4), doctest::Approx(1.0));
}

THEN("Streets without transition probabilities have empty maps") {
auto const& street2 = graph.edge(2);
auto const& transProbs2 = street2->transitionProbabilities();
CHECK_EQ(transProbs2.size(), 0);

auto const& street4 = graph.edge(4);
auto const& transProbs4 = street4->transitionProbabilities();
CHECK_EQ(transProbs4.size(), 0);
}

THEN("Other street properties are imported correctly") {
auto const& street1 = graph.edge(1);
CHECK_EQ(street1->id(), 1);
CHECK_EQ(street1->source(), 0);
CHECK_EQ(street1->target(), 1);
CHECK_EQ(street1->length(), doctest::Approx(100.5));
CHECK_EQ(street1->maxSpeed(), doctest::Approx(50.0 / 3.6));
CHECK_EQ(street1->nLanes(), 2);
CHECK_EQ(street1->name(), "Test Street A");
}
}
}
}
}
Loading