turbine-graph 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :extras do
4
+ gem 'guard', require: false
5
+ gem 'guard-rspec', require: false
6
+ gem 'ruby_gntp', require: false
7
+ gem 'rb-fsevent', '~> 0.9.1', require: false
8
+ gem 'coolline', require: false
9
+ gem 'simplecov', require: false
10
+ gem 'yard', '>= 0.8', require: false
11
+ gem 'yard-tomdoc', '>= 0.5', require: false
12
+ gem 'pry', require: false
13
+ end
14
+
15
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ watch(%r{^spec/factories/.*}) { "spec" }
6
+ end
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2012, Anthony Williams and Quintel Intelligence
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the Anthony Williams nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL ANTHONY WILLIAMS BE LIABLE FOR ANY DIRECT,
22
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # Turbine [![Build Status](https://secure.travis-ci.org/quintel/turbine.png)](http://travis-ci.org/quintel/turbine)
2
+
3
+ An in-memory directed graph written in Ruby to model an energy flow system,
4
+ a family tree, or whatever you like!
5
+
6
+ ## Quick tour
7
+
8
+ We start the console with `rake console:stub` and load the example graph:
9
+
10
+ ```ruby
11
+ graph = Turbine.energy_stub
12
+ # => #<Turbine::Graph (16 nodes, 16 edges)>
13
+ ```
14
+
15
+ ### Searching
16
+
17
+ Now you can search for a node:
18
+
19
+ ```ruby
20
+ graph.node(:space_heater_chp)
21
+ # => #<Turbine::Node key=:space_heater_chp>
22
+ ```
23
+
24
+ It will return nil when the collection is empty, or if no node with the given
25
+ key exists:
26
+
27
+ ```ruby
28
+ graph.node(:bob_ross)
29
+ # => nil
30
+ ```
31
+
32
+ ### Traversing the graph
33
+
34
+ Please see the [Terminology](#terminology) section if you are confused by the
35
+ use of **in** and **out**.
36
+
37
+ #### Adjacent nodes
38
+
39
+ Traverse the graph by requesting the inward nodes of a node:
40
+
41
+ ```ruby
42
+ graph.node(:space_heater_chp).in.to_a
43
+ # => [#<Turbine::Node>, #<Turbine::Node>, ...]
44
+ ```
45
+
46
+ Traverse the graph by requesting the outward nodes:
47
+
48
+ ```ruby
49
+ graph.node(:space_heater_chp).out.to_a
50
+ # => [#<Turbine::Node>, #<Turbine::Node>, ...]
51
+ ```
52
+
53
+ #### Filtering nodes
54
+
55
+ If you have a node and you want to get all the inward or outward nodes that
56
+ have a certain label, you can use a filter:
57
+
58
+ ```ruby
59
+ graph.node(:space_heater_chp).out(:electricity).to_a
60
+ # => [#<Turbine::Node key=:useful_demand_elec>]
61
+
62
+ graph.node(:space_heater_chp).out(:heat).to_a
63
+ # => [<Turbine::Node key=:useful_demand_heat>}>]
64
+ ```
65
+
66
+ #### Traversing edges
67
+
68
+ You can do the same for edges with `in_edges` and `out_edges`:
69
+
70
+ ```ruby
71
+ graph.node(:space_heater_chp).in_edges.to_a
72
+ # => [ #<Turbine::Edge :space_heater_coal -:heat-> :useful_demand_heat>,
73
+ # #<Turbine::Edge :space_heater_gas -:heat-> :useful_demand_heat>,
74
+ # #<Turbine::Edge :space_heater_oil -:heat-> :useful_demand_heat>,
75
+ # #<Turbine::Edge :space_heater_chp -:heat-> :useful_demand_heat> ]
76
+ ```
77
+
78
+ #### Chaining
79
+
80
+ You can also chain and step through the connections:
81
+
82
+ ```ruby
83
+ node = graph.nodes.first
84
+ node.in.in.to_a
85
+ # => [ #<Turbine::Node key=:final_demand_coal>,
86
+ # #<Turbine::Node key=:final_demand_gas>,
87
+ # #<Turbine::Node key=:final_demand_oil> ]
88
+ ```
89
+
90
+ #### Ancestors and Descendants
91
+
92
+ Alternatively, you can recursively fetch all ancestors or descendants of a
93
+ Node:
94
+
95
+ ```ruby
96
+ enum = node.ancestors.to_a
97
+ # => [#<Turbine::Node>, #<Turbine::Node>, ...]
98
+ ```
99
+
100
+ Just like with `in` and `out`, you may opt to filter the traversed nodes by
101
+ the label of the connecting edge:
102
+
103
+ ```ruby
104
+ enum = node.descendants(:likes)
105
+ # => #<Enumerator: ...>
106
+ ```
107
+
108
+ Ancestors and descendants are fetched using a breadth-first algorithm, but
109
+ depth-first is also available:
110
+
111
+ ```ruby
112
+ enum = Turbine::Traversal::DepthFirst(node, :in).to_enum
113
+ # => #<Enumerator: ...>
114
+ ```
115
+
116
+ Each adjacent node is visited no more than once during the traversal, i.e.
117
+ loops are not followed.
118
+
119
+ ### Properties / Attributes
120
+
121
+ You can set all kind of properties on a node:
122
+
123
+ ```ruby
124
+ node = graph.nodes.first
125
+ node.properties
126
+ # => {} # no properties set!
127
+
128
+ node.get(:preset_demand)
129
+ # => nil # no property called :preset_demand set!
130
+
131
+ node.set(:preset_demand, 1_000)
132
+ # => 1000
133
+
134
+ node.properties
135
+ # => {:preset_demand=>1000}
136
+ ```
137
+
138
+ ## Terminology
139
+
140
+ #### Node
141
+
142
+ In graph theory, the term **Node** and **Vertex** are used interchangeably.
143
+ Since we prefer shorter words over longer: we use node.
144
+
145
+ #### Edges
146
+
147
+ An **edge** (or sometimes called an **arc**) is a connection between two
148
+ nodes.
149
+
150
+ #### Directed graph
151
+
152
+ Turbine is a directed graph, which means that the connection between two
153
+ nodes always has a direction: it either goes from A to B or the other way
154
+ round.
155
+
156
+ #### In and out
157
+
158
+ When Node A is connected to Node B:
159
+
160
+ A --> B
161
+
162
+ A is said to be the **ancestor** of B, and B is called the **descendant** of
163
+ A. Since we like to keep things as short as possible, we choose **in** and
164
+ **out**: `A.out` results in `B`, and `B.in` results in `A`.
165
+
166
+ Hence, we have the following truth table:
167
+
168
+ | in | out
169
+ -----+-----+------
170
+ A | nil | B
171
+ -----+-----+------
172
+ B | A | nil
173
+
174
+ Still, it is up to the user to define what the direction signifies: in the
175
+ case of an energy graph: the energy flows from a coal plant to the electricity
176
+ grid. (some might argue that the demand flows from the grid to the power
177
+ plant).
178
+
179
+ In the case of the family graph, it is the ancestry of people:
180
+
181
+ parent -:child-> child
182
+
183
+ If the relation is equal, there are two edges defined and a symmetrical
184
+ relationship exists (Turbine does not support bi-directional edges):
185
+
186
+ person1 -:spouse-> person2
187
+ person2 -:spouse-> person1
188
+
189
+ i.e., person1 <-> person2
data/Rakefile ADDED
@@ -0,0 +1,126 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+
4
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
5
+ require 'turbine/version'
6
+
7
+ CLOBBER.include %w( pkg *.gem doc coverage measurements )
8
+
9
+ # Helpers --------------------------------------------------------------------
10
+
11
+ require 'date'
12
+
13
+ def replace_header(head, header_name, value)
14
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{value}'" }
15
+ end
16
+
17
+ # Build Tasks ----------------------------------------------------------------
18
+
19
+ desc 'Build the gem, and push to Github'
20
+ task :release => :build do
21
+ unless `git branch` =~ /^\* master$/
22
+ puts "You must be on the master branch to release!"
23
+ exit!
24
+ end
25
+
26
+ sh "git commit --allow-empty -a -m 'Release #{Turbine::VERSION}'"
27
+ sh "git tag v#{Turbine::VERSION}"
28
+ sh "git push origin master"
29
+ sh "git push origin v#{Turbine::VERSION}"
30
+
31
+ puts "Push to Rubygems.org with"
32
+ puts " gem push pkg/turbine-#{Turbine::VERSION}.gem"
33
+ end
34
+
35
+ desc 'Builds the gem'
36
+ task :build => [:gemspec] do
37
+ sh "mkdir -p pkg"
38
+ sh "gem build turbine.gemspec"
39
+ sh "mv turbine-graph-#{Turbine::VERSION}.gem pkg"
40
+ end
41
+
42
+ desc 'Create a fresh gemspec'
43
+ task :gemspec => :validate do
44
+ gemspec_file = File.expand_path('../turbine.gemspec', __FILE__)
45
+
46
+ # Read spec file and split out the manifest section.
47
+ spec = File.read(gemspec_file)
48
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
49
+
50
+ # Replace name version and date.
51
+ replace_header head, :name, 'turbine-graph'
52
+ replace_header head, :rubyforge_project, 'turbine-graph'
53
+ replace_header head, :version, Turbine::VERSION
54
+ replace_header head, :date, Date.today.to_s
55
+
56
+ # Determine file list from git ls-files.
57
+ files = `git ls-files`.
58
+ split("\n").
59
+ sort.
60
+ reject { |file| file =~ /^\./ }.
61
+ reject { |file| file =~ /^(doc|pkg|spec|tasks)/ }
62
+
63
+ # Format list for the gemspec.
64
+ files = files.map { |file| " #{file}" }.join("\n")
65
+
66
+ # Piece file back together and write.
67
+ manifest = " s.files = %w[\n#{files}\n ]\n"
68
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
69
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
70
+
71
+ puts "Updated #{gemspec_file}"
72
+ end
73
+
74
+ task :validate do
75
+ unless Dir['lib/*'] - %w(lib/turbine.rb lib/turbine)
76
+ puts 'The lib/ directory should only contain a turbine.rb file, and a ' \
77
+ 'turbine/ directory'
78
+ exit!
79
+ end
80
+
81
+ unless Dir['VERSION*'].empty?
82
+ puts 'A VERSION file at root level violates Gem best practices'
83
+ exit!
84
+ end
85
+ end
86
+
87
+ # Coverage -------------------------------------------------------------------
88
+
89
+ task :coverage do
90
+ ENV['COVERAGE'] = 'true'
91
+ exec 'bundle exec rspec'
92
+ end
93
+
94
+ # Documentation --------------------------------------------------------------
95
+
96
+ begin
97
+ require 'yard'
98
+ require 'yard-tomdoc'
99
+ YARD::Rake::YardocTask.new do |doc|
100
+ doc.options << '--no-highlight'
101
+ end
102
+ rescue LoadError
103
+ desc 'yard task requires that the yard gem is installed'
104
+ task :yard do
105
+ abort 'YARD is not available. In order to run yard, you must: gem ' \
106
+ 'install yard'
107
+ end
108
+ end
109
+
110
+ # Console --------------------------------------------------------------------
111
+
112
+ namespace :console do
113
+ task :run do
114
+ command = system("which pry > /dev/null 2>&1") ? 'pry' : 'irb'
115
+ exec "#{ command } -I./lib -r./lib/turbine.rb"
116
+ end
117
+
118
+ desc 'Open a pry or irb session with a stub graph on `Turbine.stub`'
119
+ task :stub do
120
+ command = system("which pry > /dev/null 2>&1") ? 'pry' : 'irb'
121
+ exec "#{ command } -I./lib -r./lib/turbine.rb -r./examples/family.rb -r./examples/energy.rb"
122
+ end
123
+ end
124
+
125
+ desc 'Open a pry or irb session preloaded with Turbine'
126
+ task console: ['console:run']
@@ -0,0 +1,80 @@
1
+ require 'turbine'
2
+
3
+ module Turbine
4
+
5
+ # This is a small Graph example, similar to the structure that is used
6
+ # in ETEngine in households.
7
+ #
8
+ # Overview:
9
+ # +------------------------------------------------------------------+
10
+ # | ud_heat <- sh_coal <- fd_coal |
11
+ # | \\\--- sh_oil <- fd_oil |
12
+ # | \\--- sh_gas <- fd_gas |
13
+ # | \--- sh_chp <--/ |
14
+ # | ud_elec <--/ |
15
+ # | \--- sh_elec <- fd_elec <- lv <- mv <- hv <- coal plant |
16
+ # | \--- elec_import |
17
+ # +------------------------------------------------------------------+
18
+ #
19
+ # Abbreviations used:
20
+ # ud = useful_demand
21
+ # sh = space_heater
22
+ # fd = final_demand
23
+ # hv = high voltage
24
+ # mv = medium voltage
25
+ # lv = low voltage
26
+ # elec = electricity
27
+ #
28
+ def self.energy_stub
29
+ graph = Turbine::Graph.new
30
+
31
+ # Nodes ------------------------------------------------------------------
32
+
33
+ useful_demand_heat = graph.add(Turbine::Node.new(:useful_demand_heat))
34
+ useful_demand_elec = graph.add(Turbine::Node.new(:useful_demand_elec))
35
+
36
+ space_heater_coal = graph.add(Turbine::Node.new(:space_heater_coal))
37
+ space_heater_gas = graph.add(Turbine::Node.new(:space_heater_gas))
38
+ space_heater_oil = graph.add(Turbine::Node.new(:space_heater_oil))
39
+ space_heater_chp = graph.add(Turbine::Node.new(:space_heater_chp))
40
+ space_heater_elec = graph.add(Turbine::Node.new(:space_heater_elec))
41
+
42
+ final_demand_coal = graph.add(Turbine::Node.new(:final_demand_coal))
43
+ final_demand_gas = graph.add(Turbine::Node.new(:final_demand_gas))
44
+ final_demand_oil = graph.add(Turbine::Node.new(:final_demand_oil))
45
+ final_demand_elec = graph.add(Turbine::Node.new(:final_demand_elec))
46
+
47
+ lv_network = graph.add(Turbine::Node.new(:lv_network))
48
+ mv_network = graph.add(Turbine::Node.new(:mv_network))
49
+ hv_network = graph.add(Turbine::Node.new(:hv_network))
50
+
51
+ coal_plant = graph.add(Turbine::Node.new(:coal_plant))
52
+ elec_import = graph.add(Turbine::Node.new(:elec_import))
53
+
54
+ # Edges ------------------------------------------------------------------
55
+
56
+ elec_import.connect_to(hv_network, :electricity)
57
+ coal_plant.connect_to(hv_network, :electricity)
58
+
59
+ hv_network.connect_to(mv_network, :electricity)
60
+ mv_network.connect_to(lv_network, :electricity)
61
+ lv_network.connect_to(final_demand_elec, :electricity)
62
+
63
+ final_demand_elec.connect_to(space_heater_elec, :electricity)
64
+ final_demand_coal.connect_to(space_heater_coal, :coal)
65
+ final_demand_gas.connect_to(space_heater_gas, :gas)
66
+ final_demand_gas.connect_to(space_heater_chp, :gas)
67
+ final_demand_oil.connect_to(space_heater_oil, :oil)
68
+
69
+ space_heater_coal.connect_to(useful_demand_heat, :heat)
70
+ space_heater_gas.connect_to(useful_demand_heat, :heat)
71
+ space_heater_oil.connect_to(useful_demand_heat, :heat)
72
+
73
+ space_heater_chp.connect_to(useful_demand_heat, :heat)
74
+ space_heater_chp.connect_to(useful_demand_elec, :electricity)
75
+
76
+ space_heater_elec.connect_to(useful_demand_elec, :electricity)
77
+
78
+ graph
79
+ end #self.stub
80
+ end #Module Turbine