Multigraphs via an edge index and edge properties/metadata#81
Multigraphs via an edge index and edge properties/metadata#81zblanco wants to merge 22 commits intobitwalker:mainfrom
Conversation
This reverts commit 8a23cd3.
|
I've been using this PR inside https://github.com/zblanco/runic for some time now as a way to keep causal runtime edges produced during DAG executions from increasing the dataflow traversal costs. It hasn't need changes and tests pass for 1.18 and might be worth reviewing. I developed this locally on Elixir 1.18+ which entailed some changes to doctests related to ordering of some results but this leaves older versions broken in CI. Not 100% on a preferred course of action - I would rather find a way to keep older versions compatible so that's what I'm looking into now. This test discrepancy may be related to OTP updates as it's not related to the actual changes. |
|
The library I've been building that depends on this PR's multigraph functionality: https://github.com/zblanco/runic is about ready to be released on Hex with some other libraries on top of that also looking to be released. Latest commits should add multigraph interaction to the rest of graph mutations and traversal / pathfinding. There are some included benchmarks that show space / time trade offs at different graph sizes with the adjacency index. I also benchmarked this PR against
The original changes I made had edge metadata changed to
Only really matters when multigraphs are actually needed with varying kinds of edges. My use case required workflow runtime memory stored as edges so this prevents dataflow traversal costs from growing with long running workflows. This includes some minor test changes to get CI passing across the Elixir/OTP versions that broke some list comparison ordering in doc tests and charlist representations but it might be worth just dropping older version support and keeping the doctests clean. @bitwalker let me know if you want any changes and/or need time to review - we can point repos at a temporary fork if needed. |
An effort to implement #18 as well as #54 for some of my own use cases. Looks like some planning work went into these in the past so not sure if there's a more preferred approach for multigraphs and/or adjacency indexing.
Ran into a performance use case for multigraphs where to minimize enumeration on traversals for I wanted an index to trade space for time.
Current approach
An opt-in flag
Graph.new(multigraph: true)with options for using a partitioned adjacency index in reflection APIs (out_edges, edges, in_edges, etc).By default this option will maintain an adjacency index partitioned by the edge label. This is overrideable with the
:partition_byoption which accepts and edge and returns a partition. E.g.Graph.new(multigraph: true, partition_by: fn edge -> edge.weight end)Reflection API options:
:by: a term or list of terms containing the partition keys.:where: a filter function which accepts an edge and returns a boolean to include or exclude it from the result.The
edge_indexis implemented as a nested map%{partition => %{vertex_id => Mapset(edge_keys)}}so the:byoption can use map access time to get the set of adjacent edges for one or more partitions.Edge Properties
For metadata / edge properties this PR changes the edge value from
%{label => edge_weight}toas well as adding the
propertiesmap to the%Edge{}struct.Todos:
fn %{label: label} -> label endto return the key