davidrichards-tegu_gears 0.0.3

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,4 @@
1
+ ---
2
+ :patch: 3
3
+ :major: 0
4
+ :minor: 0
@@ -0,0 +1,6 @@
1
+ class Factorial
2
+ include TeguGears
3
+ def process(n)
4
+ n <= 1 ? 1 : n * Factorial.call(n-1)
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # The Fibonacci numbers: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, etc.
2
+ class Fibonacci
3
+ include TeguGears
4
+ def process(n)
5
+ n <= 2 ? 1 : Fibonacci.call(n-1) + Fibonacci.call(n-2)
6
+ end
7
+ end
@@ -0,0 +1,59 @@
1
+ class SSY
2
+ include TeguGears
3
+ def process(*y)
4
+ y_mean = Mean.call(*y)
5
+ y.inject{|s,e| s + (e - y_mean)**2}
6
+ end
7
+ end
8
+
9
+ class SSX < SSY; end
10
+
11
+ class SSXY
12
+ include TeguGears
13
+ def process(x, y)
14
+ raise ArgumentError, "x and y need to be the same size" unless x.size == y.size
15
+ x_mean = Mean.call(*x)
16
+ y_mean = Mean.call(*y)
17
+ (0...x.size).inject(0.0) {|s,i| s + (y[i] - y_mean) * (x[i] - x_mean) }
18
+ end
19
+ end
20
+
21
+ class Mean
22
+ include TeguGears
23
+ def process(*x)
24
+ x.inject{|s,e| s + e} / x.size.to_f
25
+ end
26
+ end
27
+
28
+ class MaximumLikelihoodSlope
29
+ include TeguGears
30
+ def process(x, y)
31
+ raise ArgumentError, "x and y need to be the same size" unless x.size == y.size
32
+ b = SSXY.call(x, y) / SSX.call(x)
33
+ end
34
+ end
35
+
36
+ class MaximumLikelihoodIntercept
37
+ include TeguGears
38
+ def process(x,y)
39
+ raise ArgumentError, "x and y need to be the same size" unless x.size == y.size
40
+ Mean.call(*y) - MaximumLikelihoodSlope.call(x,y) * Mean.call(*x)
41
+ end
42
+ end
43
+
44
+ class SSE
45
+ include TeguGears
46
+ def process(x,y)
47
+ raise ArgumentError, "x and y need to be the same size" unless x.size == y.size
48
+ (0...x.size).inject(0.0) do |s, i|
49
+ s + (y[i] - MaximumLikelihoodIntercept.call(x,y) - MaximumLikelihoodSlope.call(x,y) * x[i] ) ** 2
50
+ end
51
+ end
52
+ end
53
+
54
+ class RSquared
55
+ include TeguGears
56
+ def process(x,y)
57
+ (SSY.call(*y) - SSE.call(x,y)) / SSY.call(*y)
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ gem 'activesupport'
3
+ require 'activesupport'
4
+
5
+ # Add to these if you want to add class and instance methods.
6
+ module TeguGears
7
+ module InstanceMethods; end
8
+ module ClassMethods; end
9
+ end
10
+
11
+ $:.unshift(File.dirname(__FILE__))
12
+ require 'tegu_gears/memoize'
13
+ Dir.glob("#{File.dirname(__FILE__)}/tegu_gears/*.rb").each { |file| require file }
14
+ module TeguGears
15
+ def self.included(base)
16
+ base.send(:include, Memoize)
17
+ base.send(:include, InstanceMethods)
18
+ base.send(:extend, ClassMethods)
19
+ end
20
+ end
21
+
22
+ # Now that I have some tools setup, require some libraries that will be
23
+ # generally useful. This will be migrated to a configurable load process
24
+ # at some point soon.
25
+
26
+ # Requires libraries, only if they're available.
27
+ def safe_load(val=nil, &block)
28
+ begin
29
+ if block
30
+ block.call
31
+ else
32
+ require val
33
+ end
34
+ # Very important that this is Exception, and not StandardError
35
+ rescue Exception => e
36
+ false
37
+ end
38
+ end
39
+
40
+ safe_load 'rubygems'
41
+ safe_load 'mathn'
42
+ safe_load {require bigdecimal; require 'bigdecimal/math'}
43
+ safe_load 'set'
44
+ safe_load 'matrix'
45
+ safe_load 'narray'
46
+ safe_load 'rnum'
47
+ safe_load 'gratr'
48
+ safe_load 'tenacious_g'
49
+ safe_load 'rbtree'
50
+
51
+ # Require the examples too, they are meant to be generally interesting or useful.
52
+ Dir.glob("#{File.dirname(__FILE__)}/examples/*.rb").each { |file| require file }
@@ -0,0 +1,25 @@
1
+ module TeguGears #:nodoc:
2
+
3
+ module InstanceMethods
4
+
5
+ # This allows me to compose two classes using TeguGears.
6
+ def compose(f)
7
+ lambda {|*args| f.call(self.call(*args))}
8
+ end
9
+ alias :| :compose
10
+ end
11
+ end
12
+
13
+ class Object
14
+ # Composes any number of functions. The first argument is the input.
15
+ # Use Struct if the argument list needs to be more expansive.
16
+ def compose(*args)
17
+ param = args.shift
18
+ args.inject {|composition, f| make_binomial_composition(composition, f)}.call(param)
19
+ end
20
+
21
+ def make_binomial_composition(f, g)
22
+ lambda{|*args| g.call(f.call(*args))}
23
+ end
24
+ private :make_binomial_composition
25
+ end
@@ -0,0 +1,3 @@
1
+ $:.<< File.dirname(__FILE__)
2
+
3
+ require 'dynamics/node'
@@ -0,0 +1,6 @@
1
+ # I'm using explicit nodes here to carry the meaning of the dynamics. I
2
+ # have done it another way, where I patch in some methods to anything
3
+ # that moves. That's a really obtrusive way to build a system, it turns
4
+ # out. So, I'll mix things in explicitly and see how that goes.
5
+ module DynamicsNode
6
+ end
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'gratr'
3
+ require 'modelling'
4
+
5
+ class Value
6
+ include Modelling
7
+ attributes :value => Proc.new {0.0}
8
+ end
9
+
10
+ class Value
11
+ attr_accessor :value
12
+ def initialize(value)
13
+ @value = value.to_f
14
+ end
15
+ end
16
+
17
+ class Beta < Value; end
18
+ class Vote < Value; end
19
+ class Income < Value; end
20
+ class Education < Value; end
21
+ class PartisanIdentification < Value; end
22
+ class NeighborhoodContext < Value; end
23
+
24
+ class Operator
25
+ include TeguGears
26
+
27
+ # Auto-collects the in-nodes, multiplying what should be multiplied
28
+ def process
29
+
30
+ args.inject { |sum, e| sum + e }
31
+ end
32
+ end
33
+
34
+
35
+ class System
36
+ class << self
37
+ def instance
38
+ @@instance ||= new
39
+ end
40
+ end
41
+
42
+ attr_reader :graph
43
+ def initialize
44
+ @graph = GRATR::Digraph.new
45
+ end
46
+
47
+ def add_edge(a,b,w=nil)
48
+ self.graph.add_edge!(a,b,w)
49
+ end
50
+
51
+ def method_missing(sym, *args, &block)
52
+ self.graph.send(sym, *args, &block)
53
+ end
54
+ end
55
+
56
+ class Object
57
+ def method_missing(sym, *args, &block)
58
+ System.instance.send(sym, *args, &block)
59
+ end
60
+ end
61
+
62
+ # swap(old, new)
@@ -0,0 +1,2 @@
1
+ class Operator
2
+ end
@@ -0,0 +1,63 @@
1
+ module TeguGears #:nodoc:
2
+
3
+ # Should get more sophisticated with a configuration tool at some point.
4
+ REPOSITORY_CLASS = Hash
5
+
6
+ # This works like a pass through to other caching tools, or keeps things
7
+ # in a hash. Each class hosting TeguGear has its own entry in this
8
+ # repository, as does each composition. Other caching tools may be
9
+ # useful to monitor the cache size, distribute it, or reduce different
10
+ # kinds of caching. I use a key/value interface, because that should
11
+ # work well with Memcache, Tokyo Cabinet, Redis, Hash, CouchDB, and
12
+ # other tools.
13
+ #
14
+ # I refactored to a centrally-managed repository because distributed
15
+ # caching doesn't make sense for expensive computations. There's
16
+ # probably another abstraction available that will allow distributed
17
+ # caching only when configured, but I'll get to that when I write a
18
+ # configuration harness.
19
+ class MemoRepository
20
+
21
+ class << self
22
+ def instance
23
+ @@instance ||= new(REPOSITORY_CLASS.new)
24
+ end
25
+
26
+ def method_missing(sym, *args, &block)
27
+ instance.send(sym, *args, &block)
28
+ end
29
+ end
30
+
31
+ attr_reader :store
32
+ def initialize(store=Hash.new)
33
+ @store = store
34
+ end
35
+
36
+ def method_missing(sym, *args, &block)
37
+ self.store.send(sym, *args, &block)
38
+ end
39
+
40
+ # Expects a hash on the other end, for now.
41
+ def set(caller, key, value)
42
+ key = simplify_key(key)
43
+ self.store[caller] ||= REPOSITORY_CLASS.new
44
+ self.store[caller][key] = value
45
+ end
46
+
47
+ # Because I opened things up to accept an array of params instead of a single param.
48
+ def simplify_key(key)
49
+ (key.is_a?(Array) and key.size == 1) ? key.first : key
50
+ end
51
+ protected :simplify_key
52
+
53
+ def for(caller, key=nil)
54
+ self.store[caller] ||= REPOSITORY_CLASS.new
55
+ key ? self.store[caller][key] : self.store[caller]
56
+ end
57
+
58
+ def flush_for(caller)
59
+ self.store[caller] = REPOSITORY_CLASS.new
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ module TeguGears #:nodoc:
2
+ # If I can get used to using a struct or a single input variable, I can
3
+ # do things this way. Otherwise, I need to get deeper into the meta
4
+ # programming to work around how to call things in a signatureless
5
+ # environment. Not an easy nut to crack, btw.
6
+ module Memoize
7
+ module ClassMethods
8
+ def instance
9
+ @inst ||= new
10
+ end
11
+
12
+ def method_missing(sym, *args, &block)
13
+ instance.send(sym, *args, &block)
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+ def call(*x)
19
+ function(*x)
20
+ end
21
+
22
+ def memoize
23
+ @memoize = true unless defined?(@memoize)
24
+ @memoize
25
+ end
26
+
27
+ def memoize=(val)
28
+ @memoize = val
29
+ end
30
+
31
+ def function(*x)
32
+ self.memoize ? memoized(*x) : process(*x)
33
+ end
34
+
35
+ def memoized(*x)
36
+ MemoRepository.for(self, x) || MemoRepository.set(self, x, process(*x))
37
+ end
38
+
39
+ def flush
40
+ MemoRepository.flush_for(self)
41
+ end
42
+
43
+ end
44
+
45
+ def self.included(base)
46
+ base.send(:extend, ClassMethods)
47
+ base.send(:include, InstanceMethods)
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,8 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ describe Factorial do
4
+ it "should be able to return the factorial of a number" do
5
+ Factorial.call(3).should eql(6)
6
+ Factorial.call(6).should eql(720)
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ describe Fibonacci do
4
+ it "should calculate the fibonacci, up to n" do
5
+ Fibonacci.call(3).should eql(2)
6
+ Fibonacci.call(4).should eql(3)
7
+ Fibonacci.call(5).should eql(5)
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ describe TeguGears, "compose" do
4
+ before(:all) do
5
+ class Square
6
+ include TeguGears
7
+ def process(n)
8
+ n ** 2
9
+ end
10
+ end
11
+
12
+ class SquareRoot
13
+ include TeguGears
14
+ def process(n)
15
+ Math.sqrt(n)
16
+ end
17
+ end
18
+ end
19
+
20
+ it "should compose two functions manually" do
21
+ square_root = SquareRoot.instance
22
+ square = Square.instance
23
+ (square_root | square).call(2).should be_close(2.0, 0.0001)
24
+ end
25
+
26
+ it "should be able to compose two functions from their host classes" do
27
+ (SquareRoot | Square).call(2).should be_close(2.0, 0.0001)
28
+ end
29
+
30
+ it "should read the composition left to right" do
31
+ plus_two = lambda{|x| x + 2}
32
+ times_two = lambda{|x| x * 2}
33
+ # IF this was right to left, it would be 10
34
+ (plus_two | times_two).call(4).should eql(12)
35
+ (times_two | plus_two).call(4).should eql(10)
36
+ end
37
+
38
+ context "from Object" do
39
+ it "should have a version available for any lambda or method" do
40
+ compose(2, SquareRoot, Square).should be_close(2.0, 0.0001)
41
+ end
42
+
43
+ it "should be able to take any number of compositions at once" do
44
+ cube_root = lambda{|x| x ** (1/3.0)}
45
+ half = lambda{|x| x / 2.0}
46
+ twice = lambda{|x| x * 2.0}
47
+ quadruple = lambda{|x| x * 4.0}
48
+ compose(2, quadruple, twice, half, cube_root).should eql(2.0)
49
+ end
50
+
51
+ it "should read left to right" do
52
+ plus_two = lambda{|x| x + 2}
53
+ times_two = lambda{|x| x * 2}
54
+ # IF this was right to left, it would be 10
55
+ compose(4, plus_two, times_two).should eql(12)
56
+ compose(4, times_two, plus_two).should eql(10)
57
+ end
58
+ end
59
+
60
+ after(:all) do
61
+ Object.send(:remove_const, :Square)
62
+ Object.send(:remove_const, :SquareRoot)
63
+ end
64
+ end
@@ -0,0 +1,83 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ describe MemoRepository do
4
+ it "should default to a hash" do
5
+ @mr = MemoRepository.new
6
+ @mr.store.should be_is_a(Hash)
7
+ end
8
+
9
+ it "should pass unknown calls to the store" do
10
+ @mr = MemoRepository.new
11
+ @mr.store.should_receive(:blah).and_return(true)
12
+ @mr.blah
13
+ end
14
+
15
+ it "should be able to take a different kind of repository" do
16
+ @mr = MemoRepository.new([])
17
+ @mr.store.should be_is_a(Array)
18
+ end
19
+
20
+ it "should provide a quasi-singleton" do
21
+ MemoRepository.instance.should be_is_a(MemoRepository)
22
+ end
23
+
24
+ it "should set the REPOSITORY_CLASS to a Hash" do
25
+ REPOSITORY_CLASS.should eql(Hash)
26
+ end
27
+
28
+ it "should construct REPOSITORY_CLASS for the singleton" do
29
+ REPOSITORY_CLASS.should_receive(:new).and_return(Hash.new)
30
+ MemoRepository.instance
31
+ end
32
+
33
+ it "should pass unknown class calls down to the instance" do
34
+ MemoRepository.instance.should_receive(:weird).and_return(true)
35
+ MemoRepository.weird
36
+ end
37
+
38
+ context "scoped access" do
39
+ before(:all) do
40
+ class A
41
+ include TeguGears
42
+ def process(n)
43
+ n + 1
44
+ end
45
+ end
46
+ end
47
+
48
+ it "should offer a set interface, that passes a scoped memo into the repository" do
49
+ MemoRepository.set(A, 1, 2)
50
+ MemoRepository.store.keys.should be_include(A)
51
+ end
52
+
53
+ context "for" do
54
+ it "should offer a default empty hash" do
55
+ MemoRepository.instance.for(:non_existent).should be_is_a(Hash)
56
+ MemoRepository.instance.for(:non_existent).should be_empty
57
+ end
58
+
59
+ it "should offer whatever cache is available" do
60
+ MemoRepository.set(A, 1, 2)
61
+ @a_repo = MemoRepository.for(A)
62
+ @a_repo.keys.should eql([1])
63
+ @a_repo.values.should eql([2])
64
+ end
65
+
66
+ it "should take an optional second parameter, which return the specific value that has been cached." do
67
+ MemoRepository.set(A, 1, 3)
68
+ MemoRepository.for(A, 1).should eql(3)
69
+ MemoRepository.flush_for(A)
70
+ end
71
+ end
72
+
73
+ it "should be able to flush a single scope" do
74
+ MemoRepository.for(A).should_not be_empty
75
+ MemoRepository.flush_for(A)
76
+ MemoRepository.for(A).should be_empty
77
+ end
78
+
79
+ after(:all) do
80
+ Object.send(:remove_const, :A)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,81 @@
1
+ require File.join(File.dirname(__FILE__), "/../../spec_helper")
2
+
3
+ describe Memoize do
4
+ before(:all) do
5
+ class A
6
+ include Memoize
7
+
8
+ def process(x)
9
+ x**2
10
+ end
11
+ end
12
+ end
13
+
14
+ context "class methods" do
15
+ it "should make instance available" do
16
+ A.should be_respond_to(:instance)
17
+ end
18
+
19
+ it "should return an instance of the host (the class that includes Memoize)" do
20
+ A.instance.should be_is_a(A)
21
+ end
22
+
23
+ it "should pass unknown calls to host instance" do
24
+ @a = A.instance
25
+ @a.should_receive(:blah).and_return(true)
26
+ A.blah
27
+ end
28
+ end
29
+
30
+ context "instance methods" do
31
+
32
+ before(:each) do
33
+ @a = A.new
34
+ end
35
+
36
+ it "should map call to function" do
37
+ @a.should_receive(:function).and_return(true)
38
+ @a.call(1)
39
+ end
40
+
41
+ it "should make memoize accessible" do
42
+ @a.memoize = false
43
+ @a.memoize.should eql(false)
44
+ @a.memoize = true
45
+ @a.memoize.should eql(true)
46
+ end
47
+
48
+ it "should default memoize to true" do
49
+ @a.memoize.should eql(true)
50
+ end
51
+
52
+ it "should expect process to define the class' core function. This is the only method that a host class needs to implement." do
53
+ @a.should_receive(:process).and_return('found me')
54
+ @a.call(:anything).should eql('found me')
55
+ end
56
+
57
+ it "should not refer to the cache when memoize is set to false" do
58
+ @a.memoize = false
59
+ MemoRepository.should_not_receive(:for)
60
+ @a.call(2)
61
+ end
62
+
63
+ it "should be able to clear the cache" do
64
+ @a.call(2)
65
+ @a.flush
66
+ MemoRepository.for(@a).should be_empty
67
+ end
68
+
69
+ it "should use the MemoRepository" do
70
+ MemoRepository.flush_for(A)
71
+ MemoRepository.for(@a)[2].should be_nil
72
+ @a.call(2)
73
+ MemoRepository.for(@a)[2].should eql(4)
74
+ end
75
+ end
76
+
77
+ after(:all) do
78
+ Object.send(:remove_const, :A)
79
+ end
80
+ end
81
+
@@ -0,0 +1,10 @@
1
+ $: << File.join(File.dirname(__FILE__), "/../lib")
2
+ require 'rubygems'
3
+ require 'spec'
4
+ require 'tegu_gears'
5
+
6
+ Spec::Runner.configure do |config|
7
+
8
+ end
9
+
10
+ include TeguGears
@@ -0,0 +1,17 @@
1
+ require File.join(File.dirname(__FILE__), "/spec_helper")
2
+
3
+ describe "TeguGears" do
4
+ it "should require rubygems" do
5
+ defined?(Gem).should eql('constant')
6
+ end
7
+
8
+ it "should require the examples" do
9
+ defined?(Fibonacci).should eql('constant')
10
+ end
11
+
12
+ it "should automatically include any sub-modules when included" do
13
+ class A; include TeguGears end
14
+ A.included_modules.should be_include(TeguGears::Memoize)
15
+ Object.send(:remove_const, :A)
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: davidrichards-tegu_gears
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - David Richards
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-28 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: TODO
17
+ email: davidlamontrichards@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - VERSION.yml
26
+ - lib/examples
27
+ - lib/examples/factorial.rb
28
+ - lib/examples/fibonacci.rb
29
+ - lib/examples/regression.rb
30
+ - lib/tegu_gears
31
+ - lib/tegu_gears/compose.rb
32
+ - lib/tegu_gears/dynamics
33
+ - lib/tegu_gears/dynamics/node.rb
34
+ - lib/tegu_gears/dynamics/noodle.rb
35
+ - lib/tegu_gears/dynamics/operator.rb
36
+ - lib/tegu_gears/dynamics.rb
37
+ - lib/tegu_gears/memo_repository.rb
38
+ - lib/tegu_gears/memoize.rb
39
+ - lib/tegu_gears.rb
40
+ - spec/lib
41
+ - spec/lib/examples
42
+ - spec/lib/examples/factorial_spec.rb
43
+ - spec/lib/examples/fibonacci_spec.rb
44
+ - spec/lib/tegu_gears
45
+ - spec/lib/tegu_gears/compose_spec.rb
46
+ - spec/lib/tegu_gears/memo_repository_spec.rb
47
+ - spec/lib/tegu_gears/memoize_spec.rb
48
+ - spec/spec_helper.rb
49
+ - spec/tegu_gears_spec.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/davidrichards/tegu_gears
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --inline-source
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.2.0
74
+ signing_key:
75
+ specification_version: 2
76
+ summary: TODO
77
+ test_files: []
78
+