Skip to content

Commit 838dbdf

Browse files
committed
Merge branch 'main' into statprob
2 parents 0f74c33 + 292021f commit 838dbdf

File tree

10 files changed

+787
-84
lines changed

10 files changed

+787
-84
lines changed

src/dsf/bindings.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ PYBIND11_MODULE(dsf_cpp, m) {
475475
dsf::g_docstrings.at("dsf::mobility::RoadDynamics::initTurnCounts").c_str())
476476
.def("updatePaths",
477477
&dsf::mobility::FirstOrderDynamics::updatePaths,
478+
pybind11::arg("throw_on_empty") = true,
478479
dsf::g_docstrings.at("dsf::mobility::RoadDynamics::updatePaths").c_str())
479480
.def(
480481
"addAgentsUniformly",

src/dsf/dsf.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
static constexpr uint8_t DSF_VERSION_MAJOR = 4;
88
static constexpr uint8_t DSF_VERSION_MINOR = 4;
9-
static constexpr uint8_t DSF_VERSION_PATCH = 6;
9+
static constexpr uint8_t DSF_VERSION_PATCH = 9;
1010

1111
static auto const DSF_VERSION =
1212
std::format("{}.{}.{}", DSF_VERSION_MAJOR, DSF_VERSION_MINOR, DSF_VERSION_PATCH);

src/dsf/mobility/Itinerary.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ namespace dsf::mobility {
3636
m_path = std::move(pathCollection);
3737
}
3838

39-
Id Itinerary::id() const { return m_id; }
40-
Id Itinerary::destination() const { return m_destination; }
41-
PathCollection const& Itinerary::path() const { return m_path; }
4239
void Itinerary::save(const std::string& fileName) const {
4340
// Open binary file
4441
std::ofstream outFile{fileName, std::ios::binary};

src/dsf/mobility/Itinerary.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,16 @@ namespace dsf::mobility {
4848

4949
/// @brief Get the itinerary's id
5050
/// @return Id, The itinerary's id
51-
Id id() const;
51+
inline auto id() const noexcept { return m_id; };
5252
/// @brief Get the itinerary's destination
5353
/// @return Id, The itinerary's destination
54-
Id destination() const;
54+
inline auto destination() const noexcept { return m_destination; };
5555
/// @brief Get the itinerary's path
5656
/// @return PathCollection const&, The itinerary's path
57-
PathCollection const& path() const;
57+
inline auto const& path() const noexcept { return m_path; };
58+
/// @brief Check if the itinerary's path is empty
59+
/// @return true if the itinerary's path is empty, false otherwise
60+
inline auto empty() const noexcept { return m_path.empty(); };
5861
/// @brief Save the itinerary to a binary file
5962
/// @param fileName The name of the file to save the itinerary to
6063
void save(const std::string& fileName) const;

src/dsf/mobility/RoadDynamics.hpp

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,10 @@ namespace dsf::mobility {
191191
void resetTurnCounts();
192192

193193
/// @brief Update the paths of the itineraries based on the given weight function
194-
void updatePaths();
194+
/// @param throw_on_empty If true, throws an exception if an itinerary has an empty path (default is true)
195+
/// If false, removes the itinerary with empty paths and the associated node from the origin/destination nodes
196+
/// @throws std::runtime_error if throw_on_empty is true and an itinerary has an empty path
197+
void updatePaths(bool const throw_on_empty = true);
195198
/// @brief Add agents uniformly on the road network
196199
/// @param nAgents The number of agents to add
197200
/// @param itineraryId The id of the itinerary to use (default is std::nullopt)
@@ -413,7 +416,6 @@ namespace dsf::mobility {
413416
for (auto const& [nodeId, weight] : this->m_destinationNodes) {
414417
m_itineraries.emplace(nodeId, std::make_unique<Itinerary>(nodeId, nodeId));
415418
}
416-
// updatePaths();
417419
std::for_each(
418420
this->graph().edges().cbegin(),
419421
this->graph().edges().cend(),
@@ -470,12 +472,6 @@ namespace dsf::mobility {
470472
471473
auto const& path{this->graph().allPathsTo(
472474
pItinerary->destination(), m_weightFunction, m_weightTreshold)};
473-
if (path.empty()) {
474-
throw std::runtime_error(
475-
std::format("No path found for itinerary {} with destination node {}",
476-
pItinerary->id(),
477-
pItinerary->destination()));
478-
}
479475
pItinerary->setPath(path);
480476
auto const newSize{pItinerary->path().size()};
481477
if (oldSize > 0 && newSize != oldSize) {
@@ -534,18 +530,21 @@ namespace dsf::mobility {
534530
double cumulativeProbability = 0.0;
535531
536532
for (const auto outEdgeId : outgoingEdges) {
537-
if (forbiddenTurns.contains(outEdgeId)) {
538-
continue;
539-
}
540-
541533
auto const& pStreetOut{this->graph().edge(outEdgeId)};
542534
543535
// Check if this is a valid path target for non-random agents
544-
bool bIsPathTarget = true;
536+
bool bIsPathTarget = false;
545537
if (!pathTargets.empty()) {
546538
bIsPathTarget =
547539
std::find(pathTargets.cbegin(), pathTargets.cend(), pStreetOut->target()) !=
548540
pathTargets.cend();
541+
}
542+
543+
if (forbiddenTurns.contains(outEdgeId) && !bIsPathTarget) {
544+
continue;
545+
}
546+
547+
if (!pathTargets.empty()) {
549548
if (!this->m_errorProbability.has_value() && !bIsPathTarget) {
550549
continue;
551550
}
@@ -570,7 +569,7 @@ namespace dsf::mobility {
570569
if (previousNodeId.has_value() && pStreetOut->target() == previousNodeId.value()) {
571570
if (pNode->isRoundabout()) {
572571
probability *= U_TURN_PENALTY_FACTOR;
573-
} else {
572+
} else if (!bIsPathTarget) {
574573
continue; // No U-turns allowed
575574
}
576575
}
@@ -581,11 +580,16 @@ namespace dsf::mobility {
581580
582581
// Select street based on weighted probabilities
583582
if (transitionProbabilities.empty()) {
584-
spdlog::trace("No valid transitions found for {} at {}", *pAgent, *pNode);
583+
spdlog::debug("No valid transitions found for {} at {}", *pAgent, *pNode);
585584
return std::nullopt;
586585
}
587586
if (transitionProbabilities.size() == 1) {
588-
return transitionProbabilities.cbegin()->first;
587+
auto const& onlyStreetId = transitionProbabilities.cbegin()->first;
588+
spdlog::debug("Only one valid transition for {} at {}: street {}",
589+
*pAgent,
590+
*pNode,
591+
onlyStreetId);
592+
return onlyStreetId;
589593
}
590594
591595
std::uniform_real_distribution<double> uniformDist{0., cumulativeProbability};
@@ -598,7 +602,10 @@ namespace dsf::mobility {
598602
}
599603
}
600604
// Return last one as fallback
601-
return std::prev(transitionProbabilities.cend())->first;
605+
auto const fallbackStreetId = std::prev(transitionProbabilities.cend())->first;
606+
spdlog::debug(
607+
"Fallback selection for {} at {}: street {}", *pAgent, *pNode, fallbackStreetId);
608+
return fallbackStreetId;
602609
}
603610
604611
template <typename delay_t>
@@ -689,15 +696,15 @@ namespace dsf::mobility {
689696
if (pStreet->isStochastic() &&
690697
uniformDist(this->m_generator) >
691698
dynamic_cast<StochasticStreet&>(*pStreet).flowRate()) {
692-
spdlog::debug("Skipping due to flow rate {:.2f} < random value",
699+
spdlog::trace("Skipping due to flow rate {:.2f} < random value",
693700
dynamic_cast<StochasticStreet&>(*pStreet).flowRate());
694701
continue;
695702
}
696703
if (i == std::ceil(transportCapacity) - 1) {
697704
double integral;
698705
double fractional = std::modf(transportCapacity, &integral);
699706
if (fractional != 0. && uniformDist(this->m_generator) > fractional) {
700-
spdlog::debug("Skipping due to fractional capacity {:.2f} < random value",
707+
spdlog::trace("Skipping due to fractional capacity {:.2f} < random value",
701708
fractional);
702709
continue;
703710
}
@@ -709,7 +716,7 @@ namespace dsf::mobility {
709716
// Logger::debug("Taking temp agent");
710717
auto const& pAgentTemp{pStreet->queue(queueIndex).front()};
711718
if (pAgentTemp->freeTime() > this->time_step()) {
712-
spdlog::debug("Skipping due to time {} < free time {}",
719+
spdlog::trace("Skipping due to time {} < free time {}",
713720
this->time_step(),
714721
pAgentTemp->freeTime());
715722
continue;
@@ -738,14 +745,14 @@ namespace dsf::mobility {
738745
pAgentTemp->setSpeed(0.);
739746
const auto& destinationNode{this->graph().node(pStreet->target())};
740747
if (destinationNode->isFull()) {
741-
spdlog::debug("Skipping due to full destination node {}", *destinationNode);
748+
spdlog::trace("Skipping due to full destination node {}", *destinationNode);
742749
continue;
743750
}
744751
if (destinationNode->isTrafficLight()) {
745752
auto& tl = dynamic_cast<TrafficLight&>(*destinationNode);
746753
auto const direction{pStreet->laneMapping().at(queueIndex)};
747754
if (!tl.isGreen(pStreet->id(), direction)) {
748-
spdlog::debug("Skipping due to red light on street {} and direction {}",
755+
spdlog::trace("Skipping due to red light on street {} and direction {}",
749756
pStreet->id(),
750757
directionToString.at(direction));
751758
continue;
@@ -1243,12 +1250,39 @@ namespace dsf::mobility {
12431250

12441251
template <typename delay_t>
12451252
requires(is_numeric_v<delay_t>)
1246-
void RoadDynamics<delay_t>::updatePaths() {
1253+
void RoadDynamics<delay_t>::updatePaths(bool const throw_on_empty) {
12471254
spdlog::debug("Init updating paths...");
1255+
tbb::concurrent_vector<Id> emptyItineraries;
12481256
tbb::parallel_for_each(
12491257
this->itineraries().cbegin(),
12501258
this->itineraries().cend(),
1251-
[this](auto const& pair) -> void { this->m_updatePath(pair.second); });
1259+
[this, throw_on_empty, &emptyItineraries](auto const& pair) -> void {
1260+
auto const& pItinerary{pair.second};
1261+
this->m_updatePath(pItinerary);
1262+
if (pItinerary->empty()) {
1263+
if (!throw_on_empty) {
1264+
spdlog::warn("No path found for itinerary {} with destination node {}",
1265+
pItinerary->id(),
1266+
pItinerary->destination());
1267+
emptyItineraries.push_back(pItinerary->id());
1268+
return;
1269+
}
1270+
throw std::runtime_error(
1271+
std::format("No path found for itinerary {} with destination node {}",
1272+
pItinerary->id(),
1273+
pItinerary->destination()));
1274+
}
1275+
});
1276+
if (!emptyItineraries.empty()) {
1277+
spdlog::warn("Removing {} itineraries with no valid path from the dynamics.",
1278+
emptyItineraries.size());
1279+
for (auto const& id : emptyItineraries) {
1280+
auto const destination = m_itineraries.at(id)->destination();
1281+
m_destinationNodes.erase(destination);
1282+
m_originNodes.erase(destination);
1283+
m_itineraries.erase(id);
1284+
}
1285+
}
12521286
spdlog::debug("End updating paths.");
12531287
}
12541288

src/dsf/mobility/RoadNetwork.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,16 @@ namespace dsf::mobility {
247247
name,
248248
geometry));
249249
// Check if there is coilcode property
250-
if (!edge_properties.at_key("coilcode").error() &&
251-
edge_properties["coilcode"].has_value()) {
252-
if (edge_properties["coilcode"].is_string()) {
253-
std::string strCoilCode{edge_properties["coilcode"].get_string().value()};
250+
if (!edge_properties.at_key("coilcode").error()) {
251+
auto const& epCoilCode = edge_properties["coilcode"];
252+
if (epCoilCode.is_string()) {
253+
std::string strCoilCode{epCoilCode.get_string().value()};
254254
addCoil(edge_id, strCoilCode);
255-
} else if (edge_properties["coilcode"].is_number()) {
256-
std::string strCoilCode =
257-
std::to_string(edge_properties["coilcode"].get_uint64());
255+
} else if (epCoilCode.is_uint64()) {
256+
std::string strCoilCode = std::to_string(epCoilCode.get_uint64());
257+
addCoil(edge_id, strCoilCode);
258+
} else if (epCoilCode.is_int64()) {
259+
std::string strCoilCode = std::to_string(epCoilCode.get_int64());
258260
addCoil(edge_id, strCoilCode);
259261
} else {
260262
spdlog::warn("Invalid coilcode for edge {}, adding default", edge_id);

test/mobility/Test_dynamics.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,27 @@ TEST_CASE("FirstOrderDynamics") {
425425
}
426426
}
427427
}
428+
GIVEN("A disconnected graph") {
429+
Street s1{0, std::make_pair(0, 1), 10.};
430+
RoadNetwork graph;
431+
graph.addStreets(s1);
432+
FirstOrderDynamics dynamics{graph, false, 69, 0., dsf::PathWeight::LENGTH};
433+
434+
WHEN(
435+
"We add an impossible itinerary (to source node) and update paths with "
436+
"throw_on_empty=true") {
437+
dynamics.addItinerary(std::unique_ptr<Itinerary>(new Itinerary(0, 0)));
438+
THEN("It throws an exception") { CHECK_THROWS(dynamics.updatePaths(true)); }
439+
}
440+
441+
WHEN(
442+
"We add an impossible itinerary (to source node) and update paths with "
443+
"throw_on_empty=false") {
444+
dynamics.addItinerary(std::unique_ptr<Itinerary>(new Itinerary(0, 0)));
445+
dynamics.updatePaths(false);
446+
THEN("The itinerary is removed") { CHECK(dynamics.itineraries().empty()); }
447+
}
448+
}
428449
}
429450
SUBCASE("Evolve") {
430451
GIVEN("A dynamics object and an itinerary") {
@@ -549,7 +570,7 @@ TEST_CASE("FirstOrderDynamics") {
549570
}
550571
}
551572
GIVEN("A simple network and an agent with forced itinerary") {
552-
spdlog::set_level(spdlog::level::trace);
573+
// spdlog::set_level(spdlog::level::trace);
553574
Street s0_1{1, std::make_pair(0, 1), 30., 15.};
554575
Street s1_0{3, std::make_pair(1, 0), 30., 15.};
555576
Street s1_2{5, std::make_pair(1, 2), 30., 15.};
@@ -586,7 +607,7 @@ TEST_CASE("FirstOrderDynamics") {
586607
CHECK_EQ(dynamics.nAgents(), 0);
587608
}
588609
}
589-
spdlog::set_level(spdlog::level::info);
610+
// spdlog::set_level(spdlog::level::info);
590611
}
591612
}
592613
SUBCASE("TrafficLights") {

0 commit comments

Comments
 (0)