Skip to content

Commit c8407d7

Browse files
Robadobmondus
authored andcommitted
HostAPI directed graph iteration and access via index.
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
1 parent b03498d commit c8407d7

File tree

7 files changed

+530
-64
lines changed

7 files changed

+530
-64
lines changed

include/flamegpu/runtime/environment/HostEnvironmentDirectedGraph.cuh

+154-37
Large diffs are not rendered by default.

include/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cuh

+51-1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ class CUDAEnvironmentDirectedGraphBuffers {
146146
* Returns the number of edges the graph is currently allocated to hold
147147
*/
148148
size_type getEdgeCount() const { return edge_count; }
149+
/**
150+
* Returns the number of vertices that have been assigned IDs
151+
*/
152+
size_type getReadyVertexCount() const { return static_cast<size_type>(h_vertex_index_map.size()); }
153+
/**
154+
* Returns the number of edges that have been assigned valid source/destination vertex pairs
155+
*/
156+
size_type getReadyEdgeCount() const { return static_cast<size_type>(h_edge_index_map.size()); }
149157
/**
150158
* Attempt to assign the provided vertex_id with an index inside h_vertex_index_map
151159
* @param vertex_id The ID of the vertex to be created
@@ -265,17 +273,59 @@ class CUDAEnvironmentDirectedGraphBuffers {
265273
* @param src_vertex_id The ID that has been assigned to the source vertex of the edge
266274
* @param dest_vertex_id The ID that has been assigned to the destination vertex of the edge
267275
*
268-
* @throws exception::IDCollision If the ID is already assigned to a different vertex
276+
* @throws exception::IDCollision If the ID source/dest pair is already assigned to a different edge
269277
*/
270278
void setEdgeSourceDestination(unsigned int edge_index, id_t src_vertex_id, id_t dest_vertex_id);
271279
/**
280+
* Updates the edge ID buffer
281+
* Updates the internal host map of src:dest->index
282+
*
283+
* @param edge_index The index of the edge
284+
* @param src_vertex_id The ID that has been assigned to the source vertex of the edge
285+
*
286+
* @throws exception::IDCollision If the ID source/dest pair is already assigned to a different edge
287+
*/
288+
void setEdgeSource(unsigned int edge_index, id_t src_vertex_id);
289+
/**
290+
* Updates the edge ID buffer
291+
* Updates the internal host map of src:dest->index
292+
*
293+
* @param edge_index The index of the edge
294+
* @param dest_vertex_id The ID that has been assigned to the destination vertex of the edge
295+
*
296+
* @throws exception::IDCollision If the ID source/dest pair is already assigned to a different edge
297+
*/
298+
void setEdgeDestination(unsigned int edge_index, id_t dest_vertex_id);
299+
/**
272300
* Returns the index of the edge with the given source and destination vertices
273301
* @param src_vertex_id The ID that has been assigned to the source vertex of the edge
274302
* @param dest_vertex_id The ID that has been assigned to the destination vertex of the edge
275303
*
276304
* @throws exception::InvalidID If the ID is not in use
277305
*/
278306
unsigned int getEdgeIndex(id_t src_vertex_id, id_t dest_vertex_id) const;
307+
/**
308+
* Returns the source vertex id of the edge with the given index
309+
* @param edge_index The index of the edge
310+
* @param stream CUDA stream to be used if data must be copied back from device
311+
*
312+
* @note ID_NOT_SET may be returned, if edge source is not yet set.
313+
*
314+
* @see getDestinationVertexID(unsigned int)
315+
* @throws exception::OutOfBoundsException If the index exceeds the number of edges
316+
*/
317+
id_t getSourceVertexID(unsigned int edge_index, cudaStream_t stream) const;
318+
/**
319+
* Returns the destination vertex id of the edge with the given index
320+
* @param edge_index The index of the edge
321+
* @param stream CUDA stream to be used if data must be copied back from device
322+
*
323+
* @note ID_NOT_SET may be returned, if edge source is not yet set.
324+
*
325+
* @see getSourceVertexID(unsigned int)
326+
* @throws exception::OutOfBoundsException If the index exceeds the number of edges
327+
*/
328+
id_t getDestinationVertexID(unsigned int edge_index, cudaStream_t stream) const;
279329
#ifdef FLAMEGPU_VISUALISATION
280330
void setVisualisation(std::shared_ptr<visualiser::ModelVisData> &_visualisation) const {
281331
this->visualisation = _visualisation;

src/flamegpu/runtime/environment/HostEnvironmentDirectedGraph.cu

+30-22
Original file line numberDiff line numberDiff line change
@@ -107,24 +107,29 @@ VertexMap HostEnvironmentDirectedGraph::vertices() {
107107
VertexMap::VertexMap(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream)
108108
: directed_graph(std::move(_directed_graph))
109109
, stream(_stream) { }
110+
size_type VertexMap::size() const {
111+
return directed_graph->getReadyVertexCount();
112+
}
113+
size_type VertexMap::allocated_size() const {
114+
return directed_graph->getVertexCount();
115+
}
116+
Vertex VertexMap::atIndex(unsigned int index) {
117+
return Vertex{ directed_graph, stream, index, true };
118+
}
110119
Vertex VertexMap::operator[](id_t vertex_id) {
111120
// Attempt to create vertex in id_map if it doesn't already exist
112121
directed_graph->createIfNotExistVertex(vertex_id, stream);
113122
// Return
114123
return Vertex{directed_graph, stream, vertex_id};
115124
}
116125

117-
Vertex::Vertex(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, id_t _vertex_id)
126+
Vertex::Vertex(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, id_t _vertex_id, bool is_index)
118127
: directed_graph(std::move(_directed_graph))
119128
, stream(_stream)
120-
, vertex_id(_vertex_id) { }
129+
, vertex_index(is_index ? _vertex_id : directed_graph->getVertexIndex(_vertex_id)) { }
121130
void Vertex::setID(id_t vertex_identifier) {
122-
// Get index
123-
const unsigned int vertex_index = directed_graph->getVertexIndex(vertex_id);
124131
// Update ID
125132
directed_graph->setVertexID(vertex_index, vertex_identifier, stream);
126-
// Update local copy of ID
127-
vertex_id = vertex_identifier;
128133
}
129134
id_t Vertex::getID() const {
130135
return getProperty<id_t>(ID_VARIABLE_NAME);
@@ -142,6 +147,16 @@ EdgeMap HostEnvironmentDirectedGraph::edges() {
142147
EdgeMap::EdgeMap(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream)
143148
: directed_graph(std::move(_directed_graph))
144149
, stream(_stream) { }
150+
151+
size_type EdgeMap::size() const {
152+
return directed_graph->getReadyEdgeCount();
153+
}
154+
size_type EdgeMap::allocated_size() const {
155+
return directed_graph->getEdgeCount();
156+
}
157+
Edge EdgeMap::atIndex(unsigned int index) {
158+
return Edge{ directed_graph, stream, index };
159+
}
145160
Edge EdgeMap::operator[](SrcDestPair source_dest_vertex_ids) {
146161
// Attempt to create edge in id_map if it doesn't already exist
147162
directed_graph->createIfNotExistEdge(source_dest_vertex_ids.first, source_dest_vertex_ids.second, stream);
@@ -152,31 +167,24 @@ Edge EdgeMap::operator[](SrcDestPair source_dest_vertex_ids) {
152167
Edge::Edge(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, id_t _source_vertex_id, id_t _dest_vertex_id)
153168
: directed_graph(std::move(_directed_graph))
154169
, stream(_stream)
155-
, source_vertex_id(_source_vertex_id)
156-
, destination_vertex_id(_dest_vertex_id) { }
170+
, edge_index(directed_graph->getEdgeIndex(_source_vertex_id, _dest_vertex_id)) { }
171+
172+
Edge::Edge(std::shared_ptr<detail::CUDAEnvironmentDirectedGraphBuffers> _directed_graph, const cudaStream_t _stream, unsigned int _edge_index)
173+
: directed_graph(std::move(_directed_graph))
174+
, stream(_stream)
175+
, edge_index(_edge_index)
176+
{ }
157177
void Edge::setSourceVertexID(id_t _source_vertex_id) {
158-
// Get index
159-
const unsigned int edge_index = directed_graph->getEdgeIndex(source_vertex_id, destination_vertex_id);
160178
// Update ID
161-
directed_graph->setEdgeSourceDestination(edge_index, _source_vertex_id, destination_vertex_id);
162-
// Update local copy of ID
163-
source_vertex_id = _source_vertex_id;
179+
directed_graph->setEdgeSource(edge_index, _source_vertex_id);
164180
}
165181
void Edge::setDestinationVertexID(id_t _dest_vertex_id) {
166-
// Get index
167-
const unsigned int edge_index = directed_graph->getEdgeIndex(source_vertex_id, destination_vertex_id);
168182
// Update ID
169-
directed_graph->setEdgeSourceDestination(edge_index, source_vertex_id, _dest_vertex_id);
170-
// Update local copy of ID
171-
destination_vertex_id = _dest_vertex_id;
183+
directed_graph->setEdgeDestination(edge_index, _dest_vertex_id);
172184
}
173185
void Edge::setSourceDestinationVertexID(id_t _source_vertex_id, id_t _dest_vertex_id) {
174-
// Get index
175-
const unsigned int edge_index = directed_graph->getEdgeIndex(source_vertex_id, destination_vertex_id);
176186
// Update ID
177187
directed_graph->setEdgeSourceDestination(edge_index, _source_vertex_id, _dest_vertex_id);
178-
// Update local copy of ID
179-
destination_vertex_id = _dest_vertex_id;
180188
}
181189
id_t Edge::getSourceVertexID() const {
182190
return getProperty<id_t, 2>(GRAPH_SOURCE_DEST_VARIABLE_NAME, 1);

src/flamegpu/simulation/detail/CUDAEnvironmentDirectedGraphBuffers.cu

+111-4
Original file line numberDiff line numberDiff line change
@@ -411,15 +411,17 @@ void CUDAEnvironmentDirectedGraphBuffers::syncDevice_async(detail::CUDAScatter&
411411
}
412412
}
413413
if (requires_rebuild && vertex_count && edge_count) {
414+
if (edge_count != h_edge_index_map.size()) {
415+
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()));
416+
} else if (vertex_count != h_vertex_index_map.size()) {
417+
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()));
418+
}
414419
// Construct the vertex ID : index map
415420
{
416421
if (vertex_id_min == std::numeric_limits<unsigned int>::max() || vertex_id_max == std::numeric_limits<unsigned int>::min()) {
417422
THROW flamegpu::exception::IDOutOfBounds("No IDs have been set, in CUDAEnvironmentDirectedGraphBuffers::syncDevice_async()");
418423
}
419424
const unsigned int ID_RANGE = 1 + vertex_id_max - vertex_id_min;
420-
if (ID_RANGE < vertex_count) {
421-
THROW flamegpu::exception::IDNotSet("Not all vertices have been assigned a unique ID, in CUDAEnvironmentDirectedGraphBuffers::syncDevice_async()");
422-
}
423425
if (d_vertex_index_map) {
424426
gpuErrchk(flamegpu::detail::cuda::cudaFree(d_vertex_index_map));
425427
}
@@ -625,7 +627,7 @@ void CUDAEnvironmentDirectedGraphBuffers::setVertexID(unsigned int vertex_index,
625627
h_vertex_index_map.erase(static_cast<id_t*>(vb.h_ptr)[vertex_index]);
626628
}
627629

628-
// Add new vertex ID to host map (validate it's not already in us)
630+
// Add new vertex ID to host map (validate it's not already in use)
629631
const auto find = h_vertex_index_map.find(vertex_id);
630632
if (find != h_vertex_index_map.end()) {
631633
THROW exception::IDCollision("ID collision, %u has already been assigned to vertex at index %u, "
@@ -688,13 +690,118 @@ void CUDAEnvironmentDirectedGraphBuffers::setEdgeSourceDestination(unsigned int
688690
// Require rebuild before use
689691
markForRebuild();
690692
}
693+
void CUDAEnvironmentDirectedGraphBuffers::setEdgeSource(unsigned int edge_index, id_t src_vertex_id) {
694+
if (edge_index >= edge_count) {
695+
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
696+
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeSource()\n", edge_index, edge_count);
697+
} else if (src_vertex_id == ID_NOT_SET) {
698+
THROW exception::IDOutOfBounds("Source vertex ID of %u is not valid, "
699+
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeSource()\n", ID_NOT_SET);
700+
}
701+
// Purge old edge src/dest from host map
702+
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
703+
// Don't need to update buffer, src_dest is not stored as ID on device
704+
id_t& edge_dest = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 0];
705+
id_t& edge_src = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 1];
706+
707+
// Remove old edge from src map if it's complete
708+
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
709+
h_edge_index_map.erase({edge_src, edge_dest});
710+
}
711+
712+
// Update edge's src dest in buffer
713+
edge_src = src_vertex_id;
714+
eb.ready = Buffer::Host;
715+
716+
// Add new edge ID to host map if it's complete
717+
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
718+
// validate it's not already in use
719+
const auto find = h_edge_index_map.find({ edge_src, edge_dest });
720+
if (find != h_edge_index_map.end()) {
721+
THROW exception::IDCollision("Edge collision, an edge has already been assigned source %u dest %u at index %u, "
722+
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeSource()\n", src_vertex_id, edge_dest, find->second);
723+
}
724+
h_edge_index_map.emplace(std::pair{src_vertex_id, edge_dest }, edge_index);
725+
}
726+
727+
// Require rebuild before use
728+
markForRebuild();
729+
}
730+
void CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination(unsigned int edge_index, id_t dest_vertex_id) {
731+
if (edge_index >= edge_count) {
732+
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
733+
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination()\n", edge_index, edge_count);
734+
} else if (dest_vertex_id == ID_NOT_SET) {
735+
THROW exception::IDOutOfBounds("Destination vertex ID of %u is not valid, "
736+
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination()\n", ID_NOT_SET);
737+
}
738+
// Purge old edge src/dest from host map
739+
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
740+
// Don't need to update buffer, src_dest is not stored as ID on device
741+
id_t& edge_dest = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 0];
742+
id_t& edge_src = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 1];
743+
744+
// Update edge's src dest in buffer
745+
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
746+
h_edge_index_map.erase({edge_src, edge_dest});
747+
}
748+
749+
// Update edge's src dest in buffer
750+
edge_dest = dest_vertex_id;
751+
eb.ready = Buffer::Host;
752+
753+
// Add new edge ID to host map if it's complete
754+
if (edge_src != ID_NOT_SET && edge_dest != ID_NOT_SET) {
755+
// validate it's not already in use
756+
const auto find = h_edge_index_map.find({ edge_src, edge_dest });
757+
if (find != h_edge_index_map.end()) {
758+
THROW exception::IDCollision("Edge collision, an edge has already been assigned source %u dest %u at index %u, "
759+
"in CUDAEnvironmentDirectedGraphBuffers::setEdgeDestination()\n", edge_src, edge_dest, find->second);
760+
}
761+
h_edge_index_map.emplace(std::pair{ edge_src, edge_dest }, edge_index);
762+
}
763+
764+
// Require rebuild before use
765+
markForRebuild();
766+
}
691767
unsigned int CUDAEnvironmentDirectedGraphBuffers::getEdgeIndex(id_t src_vertex_id, id_t dest_vertex_id) const {
692768
const auto find = h_edge_index_map.find({src_vertex_id, dest_vertex_id});
693769
if (find == h_edge_index_map.end()) {
694770
THROW exception::InvalidID("No edge found with source %u, dest %u, in CUDAEnvironmentDirectedGraphBuffers::getEdgeIndex()\n", src_vertex_id, dest_vertex_id);
695771
}
696772
return find->second;
697773
}
774+
id_t CUDAEnvironmentDirectedGraphBuffers::getSourceVertexID(unsigned int edge_index, cudaStream_t stream) const {
775+
if (edge_index >= edge_count) {
776+
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
777+
"in CUDAEnvironmentDirectedGraphBuffers::getSourceVertexID()\n", edge_index, edge_count);
778+
}
779+
// Purge old edge src/dest from host map
780+
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
781+
eb.updateHostBuffer(edge_count, stream);
782+
const unsigned int vertex_index = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 1];
783+
if (vertex_index == ID_NOT_SET)
784+
return vertex_index;
785+
auto& vb = vertex_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
786+
vb.updateHostBuffer(vertex_count, stream);
787+
return static_cast<id_t*>(vb.h_ptr)[vertex_index];
788+
}
789+
id_t CUDAEnvironmentDirectedGraphBuffers::getDestinationVertexID(unsigned int edge_index, cudaStream_t stream) const {
790+
if (edge_index >= edge_count) {
791+
THROW exception::OutOfBoundsException("Edge index exceeds bounds %u >= %u, "
792+
"in CUDAEnvironmentDirectedGraphBuffers::getDestinationVertexID()\n", edge_index, edge_count);
793+
}
794+
// Purge old edge src/dest from host map
795+
auto& eb = edge_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
796+
eb.updateHostBuffer(edge_count, stream);
797+
// Don't need to update buffer, src_dest is not stored as ID on device
798+
const unsigned int vertex_index = static_cast<id_t*>(eb.h_ptr)[edge_index * 2 + 0];
799+
if (vertex_index == ID_NOT_SET)
800+
return vertex_index;
801+
auto& vb = vertex_buffers.at(GRAPH_SOURCE_DEST_VARIABLE_NAME);
802+
vb.updateHostBuffer(vertex_count, stream);
803+
return static_cast<id_t*>(vb.h_ptr)[vertex_index];
804+
}
698805

699806
unsigned int CUDAEnvironmentDirectedGraphBuffers::createIfNotExistVertex(id_t vertex_id, const cudaStream_t stream) {
700807
if (vertex_id == ID_NOT_SET) {

swig/python/flamegpu.i

+26
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,13 @@ class ModelVis;
656656
%ignore flamegpu::DeviceEnvironmentDirectedGraph;
657657
%ignore flamegpu::EnvironmentDirectedGraphData;
658658
%ignore flamegpu::CUDAEnvironmentDirectedGraphBuffers;
659+
// Disable functions which use C++ iterators/type_index
660+
%ignore flamegpu::HostEnvironmentDirectedGraph::VertexMap::Iterator;
661+
%ignore flamegpu::HostEnvironmentDirectedGraph::VertexMap::begin;
662+
%ignore flamegpu::HostEnvironmentDirectedGraph::VertexMap::end;
663+
%ignore flamegpu::HostEnvironmentDirectedGraph::EdgeMap::Iterator;
664+
%ignore flamegpu::HostEnvironmentDirectedGraph::EdgeMap::begin;
665+
%ignore flamegpu::HostEnvironmentDirectedGraph::EdgeMap::end;
659666
%include "flamegpu/model/EnvironmentDirectedGraphDescription.cuh"
660667
%feature("flatnested"); // flat nested on to maps and children are included
661668
%include "flamegpu/runtime/environment/HostEnvironmentDirectedGraph.cuh"
@@ -840,11 +847,28 @@ namespace std {
840847
SWIG_fail;
841848
}
842849
}
850+
// Add custom python code for an iterator class, needed when swigifying iterables.
851+
%pythoncode %{
852+
class FLAMEGPUGraphMapIterator(object):
853+
854+
def __init__(self, pointerToVector):
855+
self.pointerToVector = pointerToVector
856+
self.index = -1
857+
858+
def __next__(self):
859+
self.index += 1
860+
if self.index < self.pointerToVector.allocated_size():
861+
return self.pointerToVector.atIndex(self.index)
862+
else:
863+
raise StopIteration
864+
%}
843865
// Extend VertexMap/EdgeMap so they can be accessed like a dictionary
844866
%extend flamegpu::HostEnvironmentDirectedGraph::VertexMap {
845867
%pythoncode {
846868
def __len__(self):
847869
return self.size()
870+
def __iter__(self):
871+
return FLAMEGPUGraphMapIterator(self)
848872
}
849873
flamegpu::HostEnvironmentDirectedGraph::VertexMap::Vertex flamegpu::HostEnvironmentDirectedGraph::VertexMap::__getitem__(const unsigned int vertex_id) {
850874
return $self->operator[](vertex_id);
@@ -857,6 +881,8 @@ namespace std {
857881
return self.get_item(int(src), int(dest))
858882
def __len__(self):
859883
return self.size()
884+
def __iter__(self):
885+
return FLAMEGPUGraphMapIterator(self)
860886
}
861887
flamegpu::HostEnvironmentDirectedGraph::EdgeMap::Edge flamegpu::HostEnvironmentDirectedGraph::EdgeMap::get_item(const unsigned int src, const unsigned int dest) {
862888
return $self->operator[]({src, dest});

0 commit comments

Comments
 (0)