Turbine Build Status

An in-memory directed graph written in Ruby to model an energy flow system, a family tree, or whatever you like!

Quick tour

We start the console with rake console:stub and load the example graph:

graph = Turbine.energy_stub
# => #<Turbine::Graph (16 nodes, 16 edges)>

Searching

Now you can search for a node:

graph.node(:space_heater_chp)
# => #<Turbine::Node key=:space_heater_chp>

It will return nil when the collection is empty, or if no node with the given key exists:

graph.node(:bob_ross)
# => nil

Traversing the graph

Please see the Terminology section if you are confused by the use of in and out.

Adjacent nodes

Traverse the graph by requesting the inward nodes of a node:

graph.node(:space_heater_chp).in.to_a
# => [#<Turbine::Node>, #<Turbine::Node>, ...]

Traverse the graph by requesting the outward nodes:

graph.node(:space_heater_chp).out.to_a
# => [#<Turbine::Node>, #<Turbine::Node>, ...]

Filtering nodes

If you have a node and you want to get all the inward or outward nodes that have a certain label, you can use a filter:

graph.node(:space_heater_chp).out(:electricity).to_a
# => [#<Turbine::Node key=:useful_demand_elec>]

graph.node(:space_heater_chp).out(:heat).to_a
# => [<Turbine::Node key=:useful_demand_heat>}>]

Traversing edges

You can do the same for edges with in_edges and out_edges:

graph.node(:space_heater_chp).in_edges.to_a
# => [ #<Turbine::Edge :space_heater_coal -:heat-> :useful_demand_heat>,
#      #<Turbine::Edge :space_heater_gas -:heat-> :useful_demand_heat>,
#      #<Turbine::Edge :space_heater_oil -:heat-> :useful_demand_heat>,
#      #<Turbine::Edge :space_heater_chp -:heat-> :useful_demand_heat> ]

Chaining

You can also chain and step through the connections:

node = graph.nodes.first
node.in.in.to_a
# => [ #<Turbine::Node key=:final_demand_coal>,
#      #<Turbine::Node key=:final_demand_gas>,
#      #<Turbine::Node key=:final_demand_oil> ]

Ancestors and Descendants

Alternatively, you can recursively fetch all ancestors or descendants of a Node:

enum = node.ancestors.to_a
# => [#<Turbine::Node>, #<Turbine::Node>, ...]

Just like with in and out, you may opt to filter the traversed nodes by the label of the connecting edge:

enum = node.descendants(:likes)
# => #<Enumerator: ...>

Ancestors and descendants are fetched using a breadth-first algorithm, but depth-first is also available:

enum = Turbine::Traversal::DepthFirst(node, :in).to_enum
# => #<Enumerator: ...>

Each adjacent node is visited no more than once during the traversal, i.e. loops are not followed.

Properties / Attributes

You can set all kind of properties on a node:

node = graph.nodes.first
node.properties
# => {} # no properties set!

node.get(:preset_demand)
# => nil # no property called :preset_demand set!

node.set(:preset_demand, 1_000)
# => 1000

node.properties
# => {:preset_demand=>1000}

Terminology

Node

In graph theory, the term Node and Vertex are used interchangeably. Since we prefer shorter words over longer: we use node.

Edges

An edge (or sometimes called an arc) is a connection between two nodes.

Directed graph

Turbine is a directed graph, which means that the connection between two nodes always has a direction: it either goes from A to B or the other way round.

In and out

When Node A is connected to Node B:

A --> B

A is said to be the ancestor of B, and B is called the descendant of A. Since we like to keep things as short as possible, we choose in and out: A.out results in B, and B.in results in A.

Hence, we have the following truth table:

in out
A nil B
-----+-----+------
  B  |  A  | nil

Still, it is up to the user to define what the direction signifies: in the case of an energy graph: the energy flows from a coal plant to the electricity grid. (some might argue that the demand flows from the grid to the power plant).

In the case of the family graph, it is the ancestry of people:

parent -:child-> child

If the relation is equal, there are two edges defined and a symmetrical relationship exists (Turbine does not support bi-directional edges):

person1 -:spouse-> person2
person2 -:spouse-> person1

i.e., person1 <-> person2