nodus 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+ require 'git'
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
+ gem.name = "nodus"
18
+ gem.homepage = "http://github.com/exsig/nodus"
19
+ gem.license = "MIT"
20
+ gem.summary = "Something between a Kahn Process Network and Algorithmic Skeleton for parallel pipelining and signal processing"
21
+ gem.description = %Q{EXPERIMENTAL. A form of data-flow programming based loosely on Kahn Process Networks. Will allow
22
+ for setting up operational components that can be pipelined together in a graph. Assumes all
23
+ components (nodes) are 'online' algorithms with more or less steady-state resource utilization
24
+ for continuous streams of data.}.gsub(/\s+/,' ')
25
+ gem.email = "joseph.wecker@exsig.com"
26
+ gem.authors = ["Joseph Wecker"]
27
+
28
+ # (dependencies are defined in the Gemfile)
29
+ end
30
+ Jeweler::RubygemsDotOrgTasks.new
31
+
32
+ require 'rake/testtask'
33
+ Rake::TestTask.new(:test) do |test|
34
+ test.libs << 'lib' << 'test'
35
+ test.pattern = 'test/**/test_*.rb'
36
+ test.verbose = true
37
+ end
38
+
39
+ desc "Code coverage detail"
40
+ task :simplecov do
41
+ ENV['COVERAGE'] = "true"
42
+ Rake::Task['test'].execute
43
+ end
44
+
45
+ task :default => :test
46
+
47
+ require 'rdoc/task'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ''
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "nodus #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.1
data/dia.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
11
+ require 'nodus'
12
+ include Nodus
13
+
14
+ class A < Node
15
+ input :x
16
+ output :x
17
+ end
18
+
19
+ class B < Node
20
+ input :x
21
+ output :x
22
+ end
23
+
24
+ a = A[]
25
+ b = B[]
26
+
27
+ a.add_subscriber(b)
28
+
29
+ puts a.to_dot
@@ -0,0 +1,191 @@
1
+ Composition Rules
2
+ -----------------
3
+
4
+ Compositions should be parameterized one way or another- built dynamically and allowing for variable interpolation.
5
+
6
+ Most composition nodes are given a `NodeList`, which consists of either:
7
+ * An ordered list of nodes (although order doesn't always matter)
8
+ * OR, a design-time function that calculates the list of nodes
9
+ * OR, a run-time function that calculates the list of nodes depending on token data (in which case we need to require
10
+ some sort of template / worst-case scenario etc. so that we can determine composition at a higher level? or maybe a
11
+ range specifier?)
12
+ * (with port-specifiers for individual nodes if necessary)
13
+
14
+ Most accept one or more kernels (== `lambda`, `proc`, `block`, or misc. `class` constant with specific handlers/behavior)
15
+
16
+ If a class handler, it will probably want to implement one or more of:
17
+ - Data Handler
18
+ - Upstream Exception Handler
19
+ - Downstream Exception Handler ?
20
+ - OOB Message Handler (such as N/A)
21
+
22
+ ### Core Custom Node
23
+
24
+ #### Port types:
25
+
26
+ `parameter: (optional[<default>] | required)`
27
+
28
+ `| input: (operational<output-port[s]> | consumed [control]) x (optional | required)`
29
+
30
+ `| output: ( operational<input-port[s]> | generated [control]) x (tap | primary)`
31
+
32
+
33
+
34
+ ##### Parameters
35
+ - **parameter**(default=nil): w/ optional default... specialized optional or required input port (probably not
36
+ implemented as actual message channel). Also possibly enforce the fact that it can't be connected to a stream.
37
+ Possibly composable / or able to be overridden kind of in parallel to other compositions. **optional** or
38
+ **required**. *These are also outputs. i.e., they are readable.* In fact they are intrinsicly different than normal
39
+ ports because they must be set before any real data comes through the node.
40
+
41
+ ##### Inputs
42
+ - **operational**(out-port[s]): port has paired output port that is stream-synchronized with this input.
43
+ - **control**: specialized (and implied) end-point used to help node make decisions. e.g., state / out-of-band messages
44
+ - **consumed**: End-point / Sink. Node reads input but doesn't have corresponding synchronized output.
45
+ - **optional**: Node can run without this being connected to anything (although not sure if they can connect at some
46
+ later point in time...)
47
+
48
+ ##### Outputs
49
+ - **operational**(in-port[s]): port has paired input port and this output adds to (or passes through) those input tokens.
50
+ - **controller**: Specialized (and implied) generated port used to help other nodes make decisions. Also state & out of band messages.
51
+ - **generated**: Origin / Generator. Node generates stream / it has no corresponding input port.
52
+ - **tap-point**: Output port that can optionally be tapped into (usually meaning it already has a listener within the node).
53
+
54
+
55
+ #### Helpers / Quick Builders
56
+
57
+ - **Simple Generator**
58
+ - **Simple Processor**
59
+ - **Simple Consumer** = **Simple Fold**
60
+ - **Simple Projection**
61
+
62
+ ### Axiomatic
63
+
64
+ **Node:**
65
+ * I=0..n (input ports) and O=0..n (output ports). Most of the time a static number, but sometimes a range of available
66
+ ports.
67
+
68
+ **Connector:**
69
+ * *AdHoc*
70
+ * Given an arbitrary list of nodes, allows specifying connection pairs between available inputs/outputs.
71
+ * Result is a node with all remaining unconnected input and output ports
72
+ * Can also connect values to parameter inputs
73
+ * Essentially a curry function
74
+
75
+ **Pipe:**
76
+ * *AdHoc*
77
+ * Connects member nodes on specified or default ports (specialized connector)
78
+ * All member nodes except last must have at least one output
79
+ * All member nodes except for first must have at least one input
80
+ * (like connector) result defined by unconnected inputs/outputs (usually just one main input and one main output)
81
+
82
+ **Concurrent:**
83
+ * *AdHoc*
84
+ * Executes members concurrently. Input & output streams are all inputs and outputs of members.
85
+
86
+ **Join:**
87
+ * *Stream-Synchronized*
88
+ * Token aware- meant to merge parallel branches of a single stream
89
+ * Unlimited input connections
90
+ * Listens for NOPs
91
+
92
+ **Multiply:**
93
+ * *Stream-Synchronized*
94
+ * Single input port split into specified number of output ports
95
+ * Each branch (implicitly?) has its own parallel context on the token
96
+
97
+ **View:**
98
+ * *Stream-Synchronized*
99
+ * Given a token, select a different set of fields to be the current context for downstream nodes
100
+
101
+ **Filter:**
102
+ * *Stream-Synchronized* (NOP) OR *Projection* (Drop)
103
+ * Drop or NOP tokens matching certain criteria
104
+
105
+ **Select:**
106
+ * *Stream-Synchronized* (NOP) OR *Projection* (Drop)
107
+ * Drop or NOP tokens not matching certain criteria
108
+
109
+ ### Composites
110
+
111
+
112
+ **MultiMap**
113
+ * Multiply + Concurrent(Filter or Select, View, Node) + Join
114
+
115
+ **Mux** (multiplex)
116
+ * *AdHoc* to *Stream-Synchronized*
117
+ * Multiple input streams
118
+ * Output token for each input token on _any_ input stream
119
+
120
+ **Tap**
121
+ * *Stream-Synchronized*
122
+ * A Multiply, but defined differently so that it can be injected in another node (?) without changing that node's
123
+ functionality.
124
+ * Specify tap-point of other-node when constructing.
125
+
126
+ **Runner**
127
+ * Usually automatically created
128
+ * Wraps a list of nodes into concurrent. Any inputs given stdin or integer sequences (?) and all outputs are
129
+ multiplexed to stdout.
130
+
131
+ **StateSwitch**
132
+ * Has a state port and various output streams- state port helps it decide which filter/select branches are chosen
133
+ (allow either drop or nop?)
134
+
135
+ **Split**
136
+ * Multiply + Concurrent(Filter or Select [with DROP], View, (optional Node))
137
+ * Like MultiMap except non-matching records are dropped- effectively creating unique streams
138
+
139
+ As first token propagates, it's stream-id propagates with it. Nodes that are vertically stateful and therefore need to
140
+ guarantee that the same stream is feeding them tokens at all time then use it for comparison.
141
+
142
+
143
+ Scratch
144
+ --------
145
+
146
+ Instead of streams / branches maybe just dynamic recognition of which channels have something other than a 1:1
147
+ input/output token ratio? I.e., which streams are totally synchronous so to speak...
148
+
149
+ Lifecycle
150
+ - if it has non-delayed parameterization, it spawns and sets its parameters
151
+ - it can be given the output node at creation time, parameterization-time, or any time (even after it has received tokens from
152
+ at least one inbound stream) before it tries to emit a token on that stream.
153
+ - internal state machine runs until it needs its first token (if applicable) (or until it's output queue fills up too
154
+ much)
155
+ - proceeds to run state machine
156
+
157
+
158
+
159
+ Compose Classes or Instances (or both)??
160
+
161
+
162
+ Phases
163
+ 1. Kernel-design time:
164
+ - designate input/output ports/streams
165
+ 2. Design-time:
166
+ - specify bindings as much as possible
167
+ - compose
168
+ - pre-initialize/parameterize as appropriate
169
+ - specify process network / highest level compositions
170
+ 3. Pre-runtime:
171
+ - static compliance-check
172
+ - display process network graph
173
+ - warnings / errors as appropriate
174
+ 4. Runtime:
175
+ - dynamic parameterization as appropriate
176
+ - dynamic running nodes as appropriate
177
+ - contexts and real stream instances
178
+
179
+
180
+ Specialized (out of band) input/output ports
181
+ - new output available
182
+ - new input available (?)
183
+ - output subscribed by...
184
+ - input bound by...
185
+
186
+ (allows nodes to communicate in an out-of-band fassion... easier to simply specify the peer object in initialize and
187
+ make sure every node has a general out-of-band communication channel where senders say who they are?)
188
+
189
+ * Inputs can be bound to only one output
190
+ * Outputs can be subscribed to by any number of other nodes
191
+ * Binding to a node itself assumes the correct input/output if only one of either is available
@@ -0,0 +1,89 @@
1
+
2
+
3
+
4
+
5
+ node :app, :the_gen >> :a >> :b
6
+ node :the_gen, 1..1000
7
+ node :a,
8
+
9
+ N[:app] = N[:the_gen] >> N[:a] >> N[:b]
10
+
11
+
12
+ Nodus::Node.new do
13
+ app = the_gen | a | b | stdout
14
+
15
+ the_gen(max=1000) = { 1..max } # Generator because no arity
16
+ a = {|x| x * 2 }
17
+ b = {|x| x + 100 }
18
+ end
19
+
20
+ #pseudo_tick(first) = {
21
+
22
+ # Basic:
23
+
24
+
25
+
26
+ # class TheNode < Nodus::Node
27
+ # def parameterize
28
+ # end
29
+ #
30
+ # def process
31
+ #
32
+ # end
33
+ # end
34
+
35
+ # class Ticker
36
+ # include Node
37
+ #
38
+ # def initialize(symbol)
39
+ # @symbol = symbol
40
+ # end
41
+ #
42
+ # def looped_run
43
+ # @last_price ||= 60 + rand(30)
44
+ # @last_price += @rand(-4..4)
45
+ # emit @last_price
46
+ # end
47
+ # end
48
+
49
+ class Ticker
50
+ def initialize(symbol)
51
+ @symbol = symbol
52
+ end
53
+
54
+ def each
55
+ loop do
56
+ @last_price ||= 60 + rand(30)
57
+ @last_price += @rand(-4..4)
58
+ emit [@last_price, Time.now]
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ def ticker_for(symb, high, low)
65
+ Pipe[Ticker.new(symb), Switch[->(x,_){x > high}, ->(x,t){ puts "+++ #{t}: Price above #{high}: #{x}" },
66
+ ->(x,_){x < low }, ->(x,t){ puts "--- #{t}: Price below #{low }: #{x}" }]]
67
+ end
68
+
69
+
70
+
71
+ # node to node transformations
72
+ # Composition operators NodusClass[...] expected to return another kind of Nodus class.
73
+ # --- shorthand for currying parameters (including changing the description) via inheritance
74
+ #
75
+ # parameters = standardized key/value pairs so specialization works
76
+ # = always allow lambda/proc so it gets evaluated per incoming token(?) (implies parameterization gets set
77
+ # more than once... hmmm... maybe a bad idea)
78
+ #
79
+
80
+
81
+ # P[N[:rand_dist_exp,5],
82
+ #
83
+ #
84
+ # pipe rand_dist_exp(5)
85
+ #
86
+ # junction(rand_dist_exp(5),
87
+ # rand_dist_exp(5),
88
+ # rand_dist_gaus(0,2))
89
+ #
@@ -0,0 +1,77 @@
1
+
2
+ # | Node | Input | Output |
3
+ # | ----------- | ------ | ------ |
4
+ # | Processor | 1/1 | 1/1 |
5
+ # | View | 1/1 | 1/1 |
6
+ # | Generator | 1/0 | 1/1 |
7
+ # | Branch/Tap | 1/1 | 1/n |
8
+ # | Recombine | 1/n | 1/1 |
9
+
10
+ # | Set | 0/0 | n
11
+ # | Projection | 1/1 | 1b/1b |
12
+ # | Sink | 1/1 | 1b/1b |
13
+ # | Mux | n/n*1 | 1/1 |
14
+ # | Zip | n/n*1 | 1/1 |
15
+ # | Switch | 1/1 | n/n*1 |
16
+ # | StateSwitch | 2/2*1 | n/n*1 |
17
+ # | Junction | n/m | o/p |
18
+
19
+
20
+ class Node
21
+ attr_reader :dot_style, :name
22
+ def initialize(name)
23
+ @name = name
24
+ @style_attrs = {}
25
+ style :shape, :circle
26
+ style :fontname, 'Helvetica'
27
+ style :color, '#222222'
28
+ style :fontcolor, '#444444'
29
+ end
30
+
31
+ def style(k,v)
32
+ @style_attrs[k] = v.to_s
33
+ end
34
+ end
35
+
36
+ class RootNode < Node
37
+ # (override how it's displayed)
38
+ # one or more g
39
+ end
40
+
41
+ class StandaloneNode < Node
42
+ # Starts with a generator
43
+ end
44
+
45
+ class Generator < Node
46
+ def initialize(*)
47
+ super
48
+ style :shape, :trapezium
49
+ style :orientation, 270
50
+ style :style, :filled
51
+ style :fillcolor, '#CCEEDD'
52
+ end
53
+ end
54
+
55
+
56
+ class Branch < Node
57
+ def initialize(*)
58
+ super
59
+ style :shape, :triangle
60
+ style :orientation, 90
61
+ style :style, :filled
62
+ style :fillcolor, '#EEDDCC'
63
+ style :label, ''
64
+ end
65
+ end
66
+
67
+ class Merge < Node
68
+ def initialize(*)
69
+ super
70
+ style :shape, :triangle
71
+ style :orientation, 270
72
+ style :style, :filled
73
+ style :fillcolor, '#EEDDCC'
74
+ style :label, ''
75
+ end
76
+ end
77
+