OffByOneStudios / massive-dangerzone

A platform agnostic module management system.
6 stars 1 forks source link

Node abstraction and graph library. #53

Open mason-bially opened 10 years ago

mason-bially commented 10 years ago

Need a graph library. Specifically for building wrappers around existing objects. Should live in the pyext namespace pyext.graph. Basic design goals include power, performance, and simplicity (in that order). Following is a spec for the graph library:

Graph

There are a number of abstract base classes from which "graph" objects may inherit. A graph object is an instance of configuration data for a specific graph. Specifically each class which subclasses graph features is describing how to configure and interpret a class of graphs. Instances of graph classes do not store any information (although they may cache it), specifically, graph class are meant to allow for interpretation of data from other locations.

INodeCoerce

Provides functionality for coercing nodes in the graph to provide useful information.

Base class, which inherits the INodeCoerce class as an abstract, may provide other internal abstract interfaces, provides functionality for reading graphs from sources which do not change. Hence the arguments must be immutable, or locked and enumerated on construction. (It may be useful to, in the future, provide functionality for graphs which do read mutable sources).

IWeightedEdgeGraph

Some functions require weighted edges. Every edge is assumed to weight 1 unless this interface is provided.

{
    graph.EdgeWeightNegative: false, # Describes if edge weights can be "negative".
    graph.EdgeWeightZero: 0.0 # Starting edge weight.
    graph.EdgeWeightUnreachable: float("inf") # Maximum possible value, "unreachable"
}

IWeightedNodeGraph

Some functions require weighted edges. Every node is assumed to weight 0 unless this interface is provided.

{
    graph.NodeWeightNegative: false, # Describes if node weights can be "negative".
    graph.NodeWeightZero: 0.0 # Starting node weight, if none, use node weight of initial node.
    graph.NodeWeightUnreachable: float("inf") # Maximum possible value, "unreachable"
}

Functions

Graphs

These functions manipulate graphs, returning new graph(s).

Traversals provide ways of visiting every node from a given node. Must be used as generators, as they return objects stateful to the traversal (i.e. each function returns a generator of traversal objects).

Traversal objects have the following functions and properties, mutating these without using a set function doesn't do anything:

Here are some traversals:

Generally speaking the library will be used like:

## Create a graph class:
class SomeGraph(graph.Graph):
    def graph_node_coerce(self, node):
        return node.as_some_node() # In this implementation we assume every object in the graph is aware of the graph's existence.

    def graph_node_edges(self, node):
        return node.edges() # We assume the instances returned by coerce have this edges function.

# Shortest path search:
for traverse in graph.traverse_breadth(SomeGraph(), an_object):
    if traverse.node.object is target_object:
        nodes = traverse.walk()

# Equivalent to:
nodes = graph.search(graph.traverse_breadth(SomeGraph(), an_object), target_object).walk()

# In order traversal of the inverted graph
used_by = [t.node.object for t in graph.traverse_depth(graph.reverse(SomeGraph()), query_object)]
mason-bially commented 10 years ago

This will be sufficient to replace some very brittle code having to do with generating in order lists of modules required and depended on by a module.

In addition, it will provide a useful library for traversing the new dependency system, and entity systems with relations (as opposed to writing the code twice), as well as adding new features to these systems that might otherwise be too time consuming to bother adding.

Some near future additions: