more work on dags

This commit is contained in:
Laurent Perron
2025-03-19 13:20:14 -07:00
parent 7c130df86b
commit baeaeb0be0
5 changed files with 88 additions and 91 deletions

View File

@@ -16,6 +16,7 @@
#include <cmath>
#include <limits>
#include <optional>
#include <vector>
#include "absl/algorithm/container.h"
@@ -214,6 +215,9 @@ class ConstrainedShortestPathsOnDagWrapper {
absl::Span<const NodeIndex> destinations_;
const int num_resources_;
// Set to a node if and only if this node is in both `sources_` and
// `destinations_`.
std::optional<NodeIndex> source_is_destination_ = std::nullopt;
// Data about *reachable* sub-graphs split in two for bidirectional search.
// Reachable nodes are nodes that can be reached given the resources
// constraints, i.e., for each resource, the sum of the minimum resource to
@@ -334,13 +338,15 @@ ConstrainedShortestPathsOnDagWrapper<GraphType>::
<< absl::StrFormat(
"max_resource cannot be negative not +inf nor NaN");
}
std::vector<bool> is_source(graph->num_nodes(), false);
for (const NodeIndex source : sources) {
is_source[source] = true;
}
for (const NodeIndex destination : destinations) {
CHECK(!is_source[destination])
<< "A node cannot be both a source and destination";
}
std::vector<bool> is_source(graph->num_nodes(), false);
for (const NodeIndex source : sources) {
is_source[source] = true;
}
for (const NodeIndex destination : destinations) {
if (is_source[destination]) {
source_is_destination_ = destination;
return;
}
}
@@ -542,6 +548,10 @@ template <class GraphType>
#endif
GraphPathWithLength<GraphType> ConstrainedShortestPathsOnDagWrapper<
GraphType>::RunConstrainedShortestPathOnDag() {
if (source_is_destination_.has_value()) {
return {
.length = 0, .arc_path = {}, .node_path = {*source_is_destination_}};
}
// Assign lengths on sub-relevant graphs.
std::vector<double> sub_arc_lengths[2];
for (const Direction dir : {FORWARD, BACKWARD}) {

View File

@@ -90,6 +90,19 @@ TEST(ConstrainedShortestPathOnDagTest, SimpleGraph) {
/*node_path=*/ElementsAre(source, b, destination)));
}
TEST(ConstrainedShortestPathOnDagTest, GraphWithNoArcs) {
EXPECT_THAT(ConstrainedShortestPathsOnDag(
/*num_nodes=*/1, /*arcs_with_length_and_resources=*/{},
/*source=*/0, /*destination=*/0, /*max_resources=*/{7.0}),
FieldsAre(/*length=*/0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(0)));
EXPECT_THAT(ConstrainedShortestPathsOnDag(
/*num_nodes=*/2, /*arcs_with_length_and_resources=*/{},
/*source=*/0, /*destination=*/1, /*max_resources=*/{7.0}),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ConstrainedShortestPathOnDagTest, SimpleGraphTwoPaths) {
const int source = 0;
const int destination = 1;
@@ -818,17 +831,6 @@ TEST(ConstrainedShortestPathOnDagTest, NegativeMaxResource) {
"negative");
}
TEST(ConstrainedShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int num_nodes = 1;
EXPECT_DEATH(
ConstrainedShortestPathsOnDag(
num_nodes, /*arcs_with_length_and_resources=*/{}, source, source,
/*max_resources=*/{0.0}),
"source and destination");
}
TEST(ConstrainedShortestPathsOnDagWrapperTest, ValidateTopologicalOrder) {
const int source = 0;
const int destination = 1;

View File

@@ -366,7 +366,6 @@ ShortestPathsOnDagWrapper<GraphType>::ShortestPathsOnDagWrapper(
CHECK(graph_ != nullptr);
CHECK(arc_lengths_ != nullptr);
CHECK_GT(graph_->num_nodes(), 0) << "The graph is empty: it has no nodes";
CHECK_GT(graph_->num_arcs(), 0) << "The graph is empty: it has no arcs";
#ifndef NDEBUG
CHECK_EQ(arc_lengths_->size(), graph_->num_arcs());
for (const double arc_length : *arc_lengths_) {
@@ -489,7 +488,6 @@ KShortestPathsOnDagWrapper<GraphType>::KShortestPathsOnDagWrapper(
CHECK(graph_ != nullptr);
CHECK(arc_lengths_ != nullptr);
CHECK_GT(graph_->num_nodes(), 0) << "The graph is empty: it has no nodes";
CHECK_GT(graph_->num_arcs(), 0) << "The graph is empty: it has no arcs";
CHECK_GT(path_count_, 0) << "path_count must be greater than 0";
#ifndef NDEBUG
CHECK_EQ(arc_lengths_->size(), graph_->num_arcs());

View File

@@ -78,12 +78,6 @@ TEST(ShortestPathOnDagTest, EmptyGraph) {
"num_nodes\\(\\) > 0");
}
TEST(ShortestPathOnDagTest, NoArcGraph) {
EXPECT_DEATH(ShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0),
"num_arcs\\(\\) > 0");
}
TEST(ShortestPathOnDagTest, NonExistingSourceBecauseNegative) {
EXPECT_DEATH(
ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
@@ -137,6 +131,17 @@ TEST(ShortestPathOnDagTest, SimpleGraph) {
/*node_path=*/ElementsAre(source, a, destination)));
}
TEST(ShortestPathOnDagTest, GraphsWithNoArcs) {
EXPECT_THAT(ShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0),
FieldsAre(/*length=*/0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(0)));
EXPECT_THAT(ShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/1),
FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty()));
}
TEST(ShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int destination = 1;
@@ -632,13 +637,6 @@ TEST(KShortestPathOnDagTest, EmptyGraph) {
"num_nodes\\(\\) > 0");
}
TEST(KShortestPathOnDagTest, NoArcGraph) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0, /*path_count=*/2),
"num_arcs\\(\\) > 0");
}
TEST(KShortestPathOnDagTest, NonExistingSourceBecauseNegative) {
EXPECT_DEATH(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{{0, 1, 0.0}},
@@ -689,6 +687,19 @@ TEST(KShortestPathOnDagTest, OnlyHasOnePath) {
/*node_path=*/ElementsAre(source, a, destination))));
}
TEST(KShortestPathOnDagTest, GraphsWithNoArcs) {
EXPECT_THAT(
KShortestPathsOnDag(/*num_nodes=*/1, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/0, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/0, /*arc_path=*/IsEmpty(),
/*node_path=*/ElementsAre(0))));
EXPECT_THAT(
KShortestPathsOnDag(/*num_nodes=*/2, /*arcs_with_length=*/{},
/*source=*/0, /*destination=*/1, /*path_count=*/2),
ElementsAre(FieldsAre(/*length=*/kInf, /*arc_path=*/IsEmpty(),
/*node_path=*/IsEmpty())));
}
TEST(KShortestPathOnDagTest, SourceIsDestination) {
const int source = 0;
const int destination = 1;

View File

@@ -344,6 +344,12 @@ using ArcHeadIterator =
ArcPropertyIterator<Graph, ArcIterator, typename Graph::NodeIndex,
&Graph::Head>;
// An iterator that iterates on the opposite arcs of another iterator.
template <typename Graph, typename ArcIterator>
using ArcOppositeArcIterator =
ArcPropertyIterator<Graph, ArcIterator, typename Graph::ArcIndex,
&Graph::OppositeArc>;
// Basic graph implementation without reverse arc. This class also serves as a
// documentation for the generic graph interface (minus the part related to
// reverse arcs).
@@ -595,8 +601,7 @@ class ReverseArcListGraph
using OutgoingHeadIterator =
ArcHeadIterator<ReverseArcListGraph, OutgoingArcIterator>;
using IncomingArcIterator =
ArcPropertyIterator<ReverseArcListGraph, OppositeIncomingArcIterator,
ArcIndexType, &ReverseArcListGraph::OppositeArc>;
ArcOppositeArcIterator<ReverseArcListGraph, OppositeIncomingArcIterator>;
// ReverseArcListGraph<>::OutDegree() and ::InDegree() work in O(degree).
ArcIndexType OutDegree(NodeIndexType node) const;
@@ -713,16 +718,23 @@ class ReverseArcStaticGraph
}
}
// Deprecated.
class OutgoingOrOppositeIncomingArcIterator;
using OppositeIncomingArcIterator = IntegerRangeIterator<ArcIndexType>;
class IncomingArcIterator;
using OutgoingArcIterator = IntegerRangeIterator<ArcIndexType>;
ArcIndexType OppositeArc(ArcIndexType arc) const;
// TODO(user): support Head() and Tail() before Build(), like StaticGraph<>.
NodeIndexType Head(ArcIndexType arc) const;
NodeIndexType Tail(ArcIndexType arc) const;
// ReverseArcStaticGraph<>::OutDegree() and ::InDegree() work in O(1).
ArcIndexType OutDegree(NodeIndexType node) const;
ArcIndexType InDegree(NodeIndexType node) const;
// Deprecated.
class OutgoingOrOppositeIncomingArcIterator;
using OppositeIncomingArcIterator = IntegerRangeIterator<ArcIndexType>;
using IncomingArcIterator =
ArcOppositeArcIterator<ReverseArcStaticGraph,
OppositeIncomingArcIterator>;
using OutgoingArcIterator = IntegerRangeIterator<ArcIndexType>;
IntegerRange<ArcIndexType> OutgoingArcs(NodeIndexType node) const {
return IntegerRange<ArcIndexType>(start_[node], DirectArcLimit(node));
}
@@ -746,12 +758,24 @@ class ReverseArcStaticGraph
limit);
}
BeginEndWrapper<IncomingArcIterator> IncomingArcs(NodeIndexType node) const;
BeginEndWrapper<IncomingArcIterator> IncomingArcs(NodeIndexType node) const {
const auto opposite_incoming_arcs = OppositeIncomingArcs(node);
return {IncomingArcIterator(*this, opposite_incoming_arcs.begin()),
IncomingArcIterator(*this, opposite_incoming_arcs.end())};
}
BeginEndWrapper<IncomingArcIterator> IncomingArcsStartingFrom(
NodeIndexType node, ArcIndexType from) const {
DCHECK(Base::IsNodeValid(node));
const auto opposite_incoming_arcs = OppositeIncomingArcsStartingFrom(
node, from == Base::kNilArc ? Base::kNilArc : OppositeArc(from));
return {IncomingArcIterator(*this, opposite_incoming_arcs.begin()),
IncomingArcIterator(*this, opposite_incoming_arcs.end())};
}
BeginEndWrapper<OutgoingOrOppositeIncomingArcIterator>
OutgoingOrOppositeIncomingArcs(NodeIndexType node) const;
BeginEndWrapper<IncomingArcIterator> IncomingArcsStartingFrom(
NodeIndexType node, ArcIndexType from) const;
BeginEndWrapper<OutgoingOrOppositeIncomingArcIterator>
OutgoingOrOppositeIncomingArcsStartingFrom(NodeIndexType node,
ArcIndexType from) const;
@@ -761,11 +785,6 @@ class ReverseArcStaticGraph
// graph algorithms.
absl::Span<const NodeIndexType> operator[](NodeIndexType node) const;
ArcIndexType OppositeArc(ArcIndexType arc) const;
// TODO(user): support Head() and Tail() before Build(), like StaticGraph<>.
NodeIndexType Head(ArcIndexType arc) const;
NodeIndexType Tail(ArcIndexType arc) const;
void ReserveArcs(ArcIndexType bound) override;
void AddNode(NodeIndexType node);
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head);
@@ -1592,7 +1611,6 @@ class ReverseArcListGraph<NodeIndexType,
// ReverseArcStaticGraph implementation ----------------------------------------
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph, Incoming);
DEFINE_RANGE_BASED_ARC_ITERATION(ReverseArcStaticGraph,
OutgoingOrOppositeIncoming);
@@ -1719,48 +1737,6 @@ void ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::Build(
}
}
template <typename NodeIndexType, typename ArcIndexType>
class ReverseArcStaticGraph<NodeIndexType, ArcIndexType>::IncomingArcIterator
: public OppositeIncomingArcIterator {
public:
IncomingArcIterator(const ReverseArcStaticGraph& graph, NodeIndexType node)
: limit_(graph.ReverseArcLimit(node)),
index_(graph.reverse_start_[node]),
graph_(graph) {
DCHECK(graph.IsNodeValid(node));
DCHECK_LE(index_, limit_);
}
IncomingArcIterator(const ReverseArcStaticGraph& graph, NodeIndexType node,
ArcIndexType arc)
: limit_(graph.ReverseArcLimit(node)), graph_(graph) {
index_ = arc == Base::kNilArc ? limit_
: (arc == graph.ReverseArcLimit(node)
? graph.ReverseArcLimit(node)
: graph.OppositeArc(arc));
DCHECK(graph.IsNodeValid(node));
DCHECK_GE(index_, graph.reverse_start_[node]);
DCHECK_LE(index_, limit_);
}
bool Ok() const { return index_ != limit_; }
ArcIndexType Index() const {
return this->index_ == this->limit_ ? this->limit_
: graph_.OppositeArc(this->index_);
}
void Next() {
DCHECK(Ok());
index_++;
}
DEFINE_STL_ITERATOR_FUNCTIONS(IncomingArcIterator);
private:
const ArcIndexType limit_;
ArcIndexType index_;
const ReverseArcStaticGraph& graph_;
};
template <typename NodeIndexType, typename ArcIndexType>
class ReverseArcStaticGraph<
NodeIndexType, ArcIndexType>::OutgoingOrOppositeIncomingArcIterator {