Skip to content

Commit

Permalink
HostAPI directed graph iteration and access via index.
Browse files Browse the repository at this point in the history
This necessitated updating Edge/Vertex classes to internally track via index rather than ID.
This removed redundant index lookups from every accessor, so will have negligbly improved performance.

EdgeMap and VertexMap are now franken datastructures, which have attributes of both maps and arrays.
This is great for usability, but has the potential to confuse users that don't read documentation.

Tests extended in C & Python to cover this.

Closes #1237
  • Loading branch information
Robadob committed Jan 19, 2025
1 parent a31013d commit d0546e0
Show file tree
Hide file tree
Showing 7 changed files with 530 additions and 64 deletions.
191 changes: 154 additions & 37 deletions include/flamegpu/runtime/environment/HostEnvironmentDirectedGraph.cuh

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ class CUDAEnvironmentDirectedGraphBuffers {
* Returns the number of edges the graph is currently allocated to hold
*/
size_type getEdgeCount() const { return edge_count; }
/**
* Returns the number of vertices that have been assigned IDs
*/
size_type getReadyVertexCount() const { return static_cast<size_type>(h_vertex_index_map.size()); }
/**
* Returns the number of edges that have been assigned valid source/destination vertex pairs
*/
size_type getReadyEdgeCount() const { return static_cast<size_type>(h_edge_index_map.size()); }
/**
* Attempt to assign the provided vertex_id with an index inside h_vertex_index_map
* @param vertex_id The ID of the vertex to be created
Expand Down Expand Up @@ -265,17 +273,59 @@ class CUDAEnvironmentDirectedGraphBuffers {
* @param src_vertex_id The ID that has been assigned to the source vertex of the edge
* @param dest_vertex_id The ID that has been assigned to the destination vertex of the edge
*
* @throws exception::IDCollision If the ID is already assigned to a different vertex
* @throws exception::IDCollision If the ID source/dest pair is already assigned to a different edge
*/
void setEdgeSourceDestination(unsigned int edge_index, id_t src_vertex_id, id_t dest_vertex_id);
/**
* Updates the edge ID buffer
* Updates the internal host map of src:dest->index
*
* @param edge_index The index of the edge
* @param src_vertex_id The ID that has been assigned to the source vertex of the edge
*
* @throws exception::IDCollision If the ID source/dest pair is already assigned to a different edge
*/
void setEdgeSource(unsigned int edge_index, id_t src_vertex_id);
/**
* Updates the edge ID buffer
* Updates the internal host map of src:dest->index
*
* @param edge_index The index of the edge
* @param dest_vertex_id The ID that has been assigned to the destination vertex of the edge
*
* @throws exception::IDCollision If the ID source/dest pair is already assigned to a different edge
*/
void setEdgeDestination(unsigned int edge_index, id_t dest_vertex_id);
/**
* Returns the index of the edge with the given source and destination vertices
* @param src_vertex_id The ID that has been assigned to the source vertex of the edge
* @param dest_vertex_id The ID that has been assigned to the destination vertex of the edge
*
* @throws exception::InvalidID If the ID is not in use
*/
unsigned int getEdgeIndex(id_t src_vertex_id, id_t dest_vertex_id) const;
/**
* Returns the source vertex id of the edge with the given index
* @param edge_index The index of the edge
* @param stream CUDA stream to be used if data must be copied back from device
*
* @note ID_NOT_SET may be returned, if edge source is not yet set.
*
* @see getDestinationVertexID(unsigned int)
* @throws exception::OutOfBoundsException If the index exceeds the number of edges
*/
id_t getSourceVertexID(unsigned int edge_index, cudaStream_t stream) const;
/**
* Returns the destination vertex id of the edge with the given index
* @param edge_index The index of the edge
* @param stream CUDA stream to be used if data must be copied back from device
*
* @note ID_NOT_SET may be returned, if edge source is not yet set.
*
* @see getSourceVertexID(unsigned int)
* @throws exception::OutOfBoundsException If the index exceeds the number of edges
*/
id_t getDestinationVertexID(unsigned int edge_index, cudaStream_t stream) const;
#ifdef FLAMEGPU_VISUALISATION
void setVisualisation(std::shared_ptr<visualiser::ModelVisData> &_visualisation) const {
this->visualisation = _visualisation;
Expand Down
52 changes: 30 additions & 22 deletions src/flamegpu/runtime/environment/HostEnvironmentDirectedGraph.cu
Original file line number Diff line number Diff line change
Expand Up @@ -107,24 +107,29 @@ VertexMap HostEnvironmentDirectedGraph::vertices() {
VertexMap::VertexMap(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream)
: directed_graph(std::move(_directed_graph))
, stream(_stream) { }
size_type VertexMap::size() const {
return directed_graph->getReadyVertexCount();
}
size_type VertexMap::allocated_size() const {
return directed_graph->getVertexCount();
}
Vertex VertexMap::atIndex(unsigned int index) {
return Vertex{ directed_graph, stream, index, true };
}
Vertex VertexMap::operator[](id_t vertex_id) {
// Attempt to create vertex in id_map if it doesn't already exist
directed_graph->createIfNotExistVertex(vertex_id, stream);
// Return
return Vertex{directed_graph, stream, vertex_id};
}

Vertex::Vertex(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, id_t _vertex_id)
Vertex::Vertex(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, id_t _vertex_id, bool is_index)
: directed_graph(std::move(_directed_graph))
, stream(_stream)
, vertex_id(_vertex_id) { }
, vertex_index(is_index ? _vertex_id : directed_graph->getVertexIndex(_vertex_id)) { }
void Vertex::setID(id_t vertex_identifier) {
// Get index
const unsigned int vertex_index = directed_graph->getVertexIndex(vertex_id);
// Update ID
directed_graph->setVertexID(vertex_index, vertex_identifier, stream);
// Update local copy of ID
vertex_id = vertex_identifier;
}
id_t Vertex::getID() const {
return getProperty<id_t>(ID_VARIABLE_NAME);
Expand All @@ -142,6 +147,16 @@ EdgeMap HostEnvironmentDirectedGraph::edges() {
EdgeMap::EdgeMap(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream)
: directed_graph(std::move(_directed_graph))
, stream(_stream) { }

size_type EdgeMap::size() const {
return directed_graph->getReadyEdgeCount();
}
size_type EdgeMap::allocated_size() const {
return directed_graph->getEdgeCount();
}
Edge EdgeMap::atIndex(unsigned int index) {
return Edge{ directed_graph, stream, index };
}
Edge EdgeMap::operator[](SrcDestPair source_dest_vertex_ids) {
// Attempt to create edge in id_map if it doesn't already exist
directed_graph->createIfNotExistEdge(source_dest_vertex_ids.first, source_dest_vertex_ids.second, stream);
Expand All @@ -152,31 +167,24 @@ Edge EdgeMap::operator[](SrcDestPair source_dest_vertex_ids) {
Edge::Edge(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, id_t _source_vertex_id, id_t _dest_vertex_id)
: directed_graph(std::move(_directed_graph))
, stream(_stream)
, source_vertex_id(_source_vertex_id)
, destination_vertex_id(_dest_vertex_id) { }
, edge_index(directed_graph->getEdgeIndex(_source_vertex_id, _dest_vertex_id)) { }

Edge::Edge(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, unsigned int _edge_index)
: directed_graph(std::move(_directed_graph))
, stream(_stream)
, edge_index(_edge_index)
{ }
void Edge::setSourceVertexID(id_t _source_vertex_id) {
// Get index
const unsigned int edge_index = directed_graph->getEdgeIndex(source_vertex_id, destination_vertex_id);
// Update ID
directed_graph->setEdgeSourceDestination(edge_index, _source_vertex_id, destination_vertex_id);
// Update local copy of ID
source_vertex_id = _source_vertex_id;
directed_graph->setEdgeSource(edge_index, _source_vertex_id);
}
void Edge::setDestinationVertexID(id_t _dest_vertex_id) {
// Get index
const unsigned int edge_index = directed_graph->getEdgeIndex(source_vertex_id, destination_vertex_id);
// Update ID
directed_graph->setEdgeSourceDestination(edge_index, source_vertex_id, _dest_vertex_id);
// Update local copy of ID
destination_vertex_id = _dest_vertex_id;
directed_graph->setEdgeDestination(edge_index, _dest_vertex_id);
}
void Edge::setSourceDestinationVertexID(id_t _source_vertex_id, id_t _dest_vertex_id) {
// Get index
const unsigned int edge_index = directed_graph->getEdgeIndex(source_vertex_id, destination_vertex_id);
// Update ID
directed_graph->setEdgeSourceDestination(edge_index, _source_vertex_id, _dest_vertex_id);
// Update local copy of ID
destination_vertex_id = _dest_vertex_id;
}
id_t Edge::getSourceVertexID() const {
return getProperty<id_t, 2>(GRAPH_SOURCE_DEST_VARIABLE_NAME, 1);
Expand Down
115 changes: 111 additions & 4 deletions src/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cu
Original file line number Diff line number Diff line change
Expand Up @@ -411,15 +411,17 @@ void CUDAEnvironmentDirectedGraphBuffers::syncDevice_async(detail::CUDAScatter&
}
}
if (requires_rebuild && vertex_count && edge_count) {
if (edge_count != h_edge_index_map.size()) {
THROW exception::IDNotSet("Unable to build graph, only %u/%u edges have been assigned both a source and destination, in CUDAEnvironmentDirectedGraphBuffers::syncDevice_async()", edge_count, static_cast<unsigned int>(h_edge_index_map.size()));
} else if (vertex_count != h_vertex_index_map.size()) {
THROW exception::IDNotSet("Unable to build graph, only %u/%u vertices have been assigned an ID, in CUDAEnvironmentDirectedGraphBuffers::syncDevice_async()", vertex_count, static_cast<unsigned int>(h_vertex_index_map.size()));
}
// Construct the vertex ID : index map
{
if (vertex_id_min == std::numeric_limits<unsigned int>::max() || vertex_id_max == std::numeric_limits<unsigned int>::min()) {
THROW flamegpu::exception::IDOutOfBounds("No IDs have been set, in CUDAEnvironmentDirectedGraphBuffers::syncDevice_async()");
}
const unsigned int ID_RANGE = 1 + vertex_id_max - vertex_id_min;
if (ID_RANGE < vertex_count) {
THROW flamegpu::exception::IDNotSet("Not all vertices have been assigned a unique ID, in CUDAEnvironmentDirectedGraphBuffers::syncDevice_async()");
}
if (d_vertex_index_map) {
gpuErrchk(flamegpu::detail::cuda::cudaFree(d_vertex_index_map));
}
Expand Down Expand Up @@ -625,7 +627,7 @@ void CUDAEnvironmentDirectedGraphBuffers::setVertexID(unsigned int vertex_index,
h_vertex_index_map.erase(static_cast<id_t*>(vb.h_ptr)[vertex_index]);
}

// Add new vertex ID to host map (validate it's not already in us)
// Add new vertex ID to host map (validate it's not already in use)
const auto find = h_vertex_index_map.find(vertex_id);
if (find != h_vertex_index_map.end()) {
THROW exception::IDCollision("ID collision, %u has already been assigned to vertex at index %u, "
Expand Down Expand Up @@ -688,13 +690,118 @@ void CUDAEnvironmentDirectedGraphBuffers::setEdgeSourceDestination(unsigned int
// Require rebuild before use
markForRebuild();
}
void CUDAEnvironmentDirectedGraphBuffers::setEdgeSource(unsigned int edge_index, id_t src_vertex_id) {
if (edge_index >= edge_count) {
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeSource()\n", edge_index, edge_count);
} else if (src_vertex_id == ID_NOT_SET) {
THROW exception::IDOutOfBounds("Source vertex ID of %u is not valid, "
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeSource()\n", ID_NOT_SET);
}
// Purge old edge src/dest from host map
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
// Don't need to update buffer, src_dest is not stored as ID on device
id_t& edge_dest = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 0];
id_t& edge_src = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 1];

// Remove old edge from src map if it's complete
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
h_edge_index_map.erase({edge_src, edge_dest});
}

// Update edge's src dest in buffer
edge_src = src_vertex_id;
eb.ready = Buffer::Host;

// Add new edge ID to host map if it's complete
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
// validate it's not already in use
const auto find = h_edge_index_map.find({ edge_src, edge_dest });
if (find != h_edge_index_map.end()) {
THROW exception::IDCollision("Edge collision, an edge has already been assigned source %u dest %u at index %u, "
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeSource()\n", src_vertex_id, edge_dest, find->second);
}
h_edge_index_map.emplace(std::pair{src_vertex_id, edge_dest }, edge_index);
}

// Require rebuild before use
markForRebuild();
}
void CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination(unsigned int edge_index, id_t dest_vertex_id) {
if (edge_index >= edge_count) {
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination()\n", edge_index, edge_count);
} else if (dest_vertex_id == ID_NOT_SET) {
THROW exception::IDOutOfBounds("Destination vertex ID of %u is not valid, "
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination()\n", ID_NOT_SET);
}
// Purge old edge src/dest from host map
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
// Don't need to update buffer, src_dest is not stored as ID on device
id_t& edge_dest = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 0];
id_t& edge_src = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 1];

// Update edge's src dest in buffer
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
h_edge_index_map.erase({edge_src, edge_dest});
}

// Update edge's src dest in buffer
edge_dest = dest_vertex_id;
eb.ready = Buffer::Host;

// Add new edge ID to host map if it's complete
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
// validate it's not already in use
const auto find = h_edge_index_map.find({ edge_src, edge_dest });
if (find != h_edge_index_map.end()) {
THROW exception::IDCollision("Edge collision, an edge has already been assigned source %u dest %u at index %u, "
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination()\n", edge_src, edge_dest, find->second);
}
h_edge_index_map.emplace(std::pair{ edge_src, edge_dest }, edge_index);
}

// Require rebuild before use
markForRebuild();
}
unsigned int CUDAEnvironmentDirectedGraphBuffers::getEdgeIndex(id_t src_vertex_id, id_t dest_vertex_id) const {
const auto find = h_edge_index_map.find({src_vertex_id, dest_vertex_id});
if (find == h_edge_index_map.end()) {
THROW exception::InvalidID("No edge found with source %u, dest %u, in CUDAEnvironmentDirectedGraphBuffers::getEdgeIndex()\n", src_vertex_id, dest_vertex_id);
}
return find->second;
}
id_t CUDAEnvironmentDirectedGraphBuffers::getSourceVertexID(unsigned int edge_index, cudaStream_t stream) const {
if (edge_index >= edge_count) {
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
"in CUDAEnvironmentDirectedGraphBuffers::getSourceVertexID()\n", edge_index, edge_count);
}
// Purge old edge src/dest from host map
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
eb.updateHostBuffer(edge_count, stream);
const unsigned int vertex_index = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 1];
if (vertex_index == ID_NOT_SET)
return vertex_index;
auto& vb = vertex_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
vb.updateHostBuffer(vertex_count, stream);
return static_cast<id_t*>(vb.h_ptr)[vertex_index];
}
id_t CUDAEnvironmentDirectedGraphBuffers::getDestinationVertexID(unsigned int edge_index, cudaStream_t stream) const {
if (edge_index >= edge_count) {
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
"in CUDAEnvironmentDirectedGraphBuffers::getDestinationVertexID()\n", edge_index, edge_count);
}
// Purge old edge src/dest from host map
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
eb.updateHostBuffer(edge_count, stream);
// Don't need to update buffer, src_dest is not stored as ID on device
const unsigned int vertex_index = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 0];
if (vertex_index == ID_NOT_SET)
return vertex_index;
auto& vb = vertex_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
vb.updateHostBuffer(vertex_count, stream);
return static_cast<id_t*>(vb.h_ptr)[vertex_index];
}

unsigned int CUDAEnvironmentDirectedGraphBuffers::createIfNotExistVertex(id_t vertex_id, const cudaStream_t stream) {
if (vertex_id == ID_NOT_SET) {
Expand Down
26 changes: 26 additions & 0 deletions swig/python/flamegpu.i
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,13 @@ class ModelVis;
%ignore flamegpu::DeviceEnvironmentDirectedGraph;
%ignore flamegpu::EnvironmentDirectedGraphData;
%ignore flamegpu::CUDAEnvironmentDirectedGraphBuffers;
// Disable functions which use C++ iterators/type_index
%ignore flamegpu::HostEnvironmentDirectedGraph::VertexMap::Iterator;
%ignore flamegpu::HostEnvironmentDirectedGraph::VertexMap::begin;
%ignore flamegpu::HostEnvironmentDirectedGraph::VertexMap::end;
%ignore flamegpu::HostEnvironmentDirectedGraph::EdgeMap::Iterator;
%ignore flamegpu::HostEnvironmentDirectedGraph::EdgeMap::begin;
%ignore flamegpu::HostEnvironmentDirectedGraph::EdgeMap::end;
%include "flamegpu/model/EnvironmentDirectedGraphDescription.cuh"
%feature("flatnested"); // flat nested on to maps and children are included
%include "flamegpu/runtime/environment/HostEnvironmentDirectedGraph.cuh"
Expand Down Expand Up @@ -840,11 +847,28 @@ namespace std {
SWIG_fail;
}
}
// Add custom python code for an iterator class, needed when swigifying iterables.
%pythoncode %{
class FLAMEGPUGraphMapIterator(object):

def __init__(self, pointerToVector):
self.pointerToVector = pointerToVector
self.index = -1

def __next__(self):
self.index += 1
if self.index < self.pointerToVector.allocated_size():
return self.pointerToVector.atIndex(self.index)
else:
raise StopIteration
%}
// Extend VertexMap/EdgeMap so they can be accessed like a dictionary
%extend flamegpu::HostEnvironmentDirectedGraph::VertexMap {
%pythoncode {
def __len__(self):
return self.size()
def __iter__(self):
return FLAMEGPUGraphMapIterator(self)
}
flamegpu::HostEnvironmentDirectedGraph::VertexMap::Vertex flamegpu::HostEnvironmentDirectedGraph::VertexMap::__getitem__(const unsigned int vertex_id) {
return $self->operator[](vertex_id);
Expand All @@ -857,6 +881,8 @@ namespace std {
return self.get_item(int(src), int(dest))
def __len__(self):
return self.size()
def __iter__(self):
return FLAMEGPUGraphMapIterator(self)
}
flamegpu::HostEnvironmentDirectedGraph::EdgeMap::Edge flamegpu::HostEnvironmentDirectedGraph::EdgeMap::get_item(const unsigned int src, const unsigned int dest) {
return $self->operator[]({src, dest});
Expand Down
Loading

0 comments on commit d0546e0

Please sign in to comment.