integrator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,7 @@
1
+ == 0.0.1 2007-10-13
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
5
+
6
+ * Added test code
7
+ * Only for Cash-Karp RK45 fixed and adaptive integrators
data/License.txt ADDED
@@ -0,0 +1,5 @@
1
+ Originally Copyright (c) 2007 Noah Gibbs
2
+
3
+ This code is released into the public domain by the author in 2007. It may
4
+ be used for any purpose whatsoever, though it comes with no warranty of any
5
+ kind.
data/Manifest.txt ADDED
@@ -0,0 +1,27 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/integrator.rb
9
+ lib/integrator/adaptive.rb
10
+ lib/integrator/rkck.rb
11
+ lib/integrator/rkqs.rb
12
+ lib/integrator/version.rb
13
+ log/debug.log
14
+ script/destroy
15
+ script/generate
16
+ script/txt2html
17
+ setup.rb
18
+ tasks/deployment.rake
19
+ tasks/environment.rake
20
+ tasks/website.rake
21
+ test/test_helper.rb
22
+ test/test_integrator.rb
23
+ website/index.html
24
+ website/index.txt
25
+ website/javascripts/rounded_corners_lite.inc.js
26
+ website/stylesheets/screen.css
27
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,3 @@
1
+ This gem includes a framework for numerical integrators, both fixed-step and
2
+ adaptive, and includes an excellent sample integrator of each type to get
3
+ you started.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'integrator/version'
2
+
3
+ AUTHOR = 'Noah Gibbs'
4
+ EMAIL = "angelbob@nospam.users.sourceforge.net"
5
+ DESCRIPTION = "Numerical integration framework"
6
+ GEM_NAME = 'integrator' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'diffeq' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Integrator::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'integrator documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
62
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'integrator'
@@ -0,0 +1,109 @@
1
+ #
2
+ # Integrator library
3
+ # Copyright (C) 2007 Noah Gibbs
4
+ #
5
+ # This library is in the public domain, and may be redistributed in any
6
+ # way.
7
+ #
8
+
9
+ require "matrix"
10
+
11
+ require "rubygems"
12
+ require "integrator"
13
+
14
+ require "integrator/rkqs.rb"
15
+
16
+ # This is the top-level driver, with logging, for Cash-Karp adaptive
17
+ # Runge-Kutta fourth-order integration with error checking. The
18
+ # algorithm is from "Numerical Methods in C", second edition.
19
+
20
+ class Adaptive_Cash_Karp_RK45 < OneStep_Cash_Karp_RK45
21
+ include Integrator
22
+
23
+ MAXSTP = 10000
24
+ TINY = 1.0e-30
25
+
26
+ def initialize()
27
+ Integrator_initialize()
28
+ end
29
+
30
+ def adaptive_integrate(ystart, x1, x2, eps, h1, hmin, derivs)
31
+
32
+ x = x1
33
+ h1 = -h1 unless h1 >= 0
34
+ x2 - x1 >= 0 ? h = h1 : h = -h1
35
+ @nok = @nbad = @count = 0
36
+
37
+ y = ystart
38
+ xsav = x - @dxsav * 2.0 if @kmax > 0
39
+
40
+ nstp = 1
41
+ while nstp <= MAXSTP
42
+ dydx = derivs.call(x, y)
43
+ #yscal = y.abs + (dydx * h).abs + Vector( [TINY] * y.length)
44
+ yscal = y.collect2(dydx) { |yi, dyi| yi.abs + (dyi * h).abs + TINY }
45
+
46
+ if @kmax > 0 and @count < @kmax - 1 and (x-xsav).abs > @dxsav.abs
47
+ @count += 1
48
+ @xp[@count] = x
49
+ @yp[@count] = y
50
+ xsav = x
51
+ end
52
+
53
+ h = x2 - x if ((x + h - x2)*(x + h - x1) > 0.0)
54
+ x, y, hdid, hnext = integrate_ad_step(y, dydx, x, h, eps,
55
+ yscal, derivs)
56
+ if hdid == h
57
+ @nok += 1
58
+ else
59
+ @nbad += 1
60
+ end
61
+
62
+ if (x-x2)*(x2-x1) >= 0.0
63
+ if @kmax != 0
64
+ @count += 1
65
+ @xp[@count] = x
66
+ @yp[@count] = y
67
+ end
68
+ return y
69
+ end
70
+
71
+ raise "Step size too small!" if hnext.abs <= hmin
72
+ h = hnext
73
+
74
+ nstp += 1
75
+ end
76
+
77
+ raise "Too many steps in integration!"
78
+ end
79
+
80
+ end
81
+
82
+ if __FILE__ == $0
83
+
84
+ rk45 = Adaptive_Cash_Karp_RK45.new()
85
+
86
+ def assert_within_epsilon(actual, expected)
87
+ if expected.kind_of?(Vector)
88
+ actual.collect2(expected) { |x, y|
89
+ assert_within_epsilon(x, y)
90
+ }
91
+ else
92
+ (actual - expected).abs / expected < 0.001 or
93
+ raise "Values don't match! Actual was #{actual}, not #{expected}!"
94
+ end
95
+ end
96
+ assert_within_epsilon(10000.0, 10001)
97
+
98
+ # This takes the derivative of e^x for each vector element.
99
+ # The derivative of e^x is just e^x again.
100
+ exp_deriv = proc { |x, y| y.collect { |yi| yi } }
101
+
102
+ exp_start = Vector.elements([1.0] * 100)
103
+
104
+ y = rk45.adaptive_integrate(exp_start, 0.0, 1.0, 0.001, 0.01, 0.0001,
105
+ exp_deriv)
106
+ assert_within_epsilon(y, Vector.elements( [ Math::E ] * 100 ))
107
+
108
+ print "All tests completed...\n"
109
+ end
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ #
4
+ # Integrator library
5
+ # Copyright (C) 2007 Noah Gibbs
6
+ #
7
+ # This library is in the public domain, and may be redistributed in any
8
+ # way.
9
+ #
10
+
11
+ # Cash-Karp embedded Runge-Kutta integration This algorithm is taken
12
+ # from "Numerical Recipes in C", second edition, though the ruby
13
+ # translation is my own. This is the fixed step-size version.
14
+
15
+ require "matrix"
16
+
17
+ class Cash_Karp_RK4
18
+ A2 = 0.2
19
+ A3 = 0.3
20
+ A4 = 0.6
21
+ A5 = 1.0
22
+ A6 = 0.875
23
+ B21 = 0.2
24
+ B31 = 3.0/40.0
25
+ B32 = 9.0/40.0
26
+ B41 = 0.3
27
+ B42 = -0.9
28
+ B43 = 1.2
29
+ B51 = -11.0/54.0
30
+ B52 = 2.5
31
+ B53 = -70.0/27.0
32
+ B54 = 35.0/27.0
33
+ B61 = 1631.0/55296.0
34
+ B62 = 175.0/512.0
35
+ B63 = 575.0/13824.0
36
+ B64 = 44275.0/110592.0
37
+ B65 = 253.0/4096.0
38
+ C1 = 37.0/378.0
39
+ C3 = 250.0/621.0
40
+ C4 = 125.0/594.0
41
+ C6 = 512.0/1771.0
42
+ DC1 = C1-2825.0/27648.0
43
+ DC3 = C3-18575.0/48384.0
44
+ DC4 = C4-13525.0/55296.0
45
+ DC5 = -277.00/14336.0
46
+ DC6 = C6-0.25
47
+
48
+ def integrate_fixed_step(y, dydx, x, h, derivs)
49
+ raise "Bad type!" unless [y, dydx].all? { |var| var.kind_of?(Vector) }
50
+ raise "Bad type!" unless [x, h].all? { |var| var.kind_of?(Float) }
51
+ raise "Bad method!" unless derivs.kind_of?(Proc)
52
+
53
+ ytemp = y + dydx * B21 * h
54
+ ak2 = derivs.call(x + A2*h, ytemp)
55
+ ytemp = y + (dydx * B31 + ak2 * B32) * h
56
+ ak3 = derivs.call(x + A3*h, ytemp)
57
+ ytemp = y + (dydx * B41 + ak2 * B42 + ak3 * B43) * h
58
+ ak4 = derivs.call(x + A4*h, ytemp)
59
+ ytemp = y + (dydx * B51 + ak2 * B52 + ak3 * B53 + ak4 * B54) * h
60
+ ak5 = derivs.call(x + A5*h, ytemp)
61
+ ytemp = y + (dydx * B61 + ak2 * B62 + ak3 * B63 + ak4 * B64 +
62
+ ak5 * B65) * h
63
+ ak6 = derivs.call(x + A6*h, ytemp)
64
+ yout = y + (dydx * C1 + ak3 * C3 + ak4 * C4 + ak6 * C6) * h
65
+ yerr = (dydx * DC1 + ak3 * DC3 + ak4 * DC4 + ak5 * DC5 + ak6 * DC6) * h
66
+
67
+ # Return new values and error terms
68
+ [yout, yerr]
69
+ end
70
+ end
71
+
72
+ if __FILE__ == $0
73
+
74
+ def assert_within_epsilon(actual, expected)
75
+ if expected.kind_of?(Vector)
76
+ expected.collect2(actual) { |x, y|
77
+ assert_within_epsilon(x, y)
78
+ }
79
+ else
80
+ (actual - expected).abs / expected < 0.001 or
81
+ raise "Values don't match! Actual was #{actual}, not #{expected}!"
82
+ end
83
+ end
84
+ assert_within_epsilon(10000.0, 10000.00001)
85
+
86
+ const_deriv = proc do |x, y|
87
+ y.collect { |yi| 0.1 }
88
+ end
89
+
90
+ exp_deriv = proc do |x, y|
91
+ y.collect { |yi| yi }
92
+ end
93
+
94
+ integ = Cash_Karp_RK4.new()
95
+
96
+ constvec = Vector.elements([1.0] * 100)
97
+ constderiv = const_deriv.call(0, constvec)
98
+
99
+ # Integrate a constant derivative over a distance of 1.0
100
+ yout, yerr = integ.integrate_fixed_step(constvec, constderiv, 0.0, 1.0,
101
+ const_deriv)
102
+ assert_within_epsilon(yout, Vector.elements([ 1.1 ] * 100))
103
+
104
+ onevec = Vector.elements([1.0] * 100)
105
+ expderiv = exp_deriv.call(0, onevec)
106
+ dist = 0.01 # Keep this small because we only make one integration step
107
+ # Integrate e^x over a distance of 1.0, starting at e^0 == 1.0
108
+ yout, yerr = integ.integrate_fixed_step(onevec, expderiv, 0.0, dist,
109
+ exp_deriv)
110
+ assert_within_epsilon(yout, Vector.elements([ Math::E ** dist ] * 100))
111
+
112
+ print "Tests completed...\n"
113
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ #
4
+ # Integrator library
5
+ # Copyright (C) 2007 Noah Gibbs
6
+ #
7
+ # This library is in the public domain, and may be redistributed in any
8
+ # way.
9
+ #
10
+
11
+ require "matrix"
12
+ require "integrator/rkck.rb"
13
+
14
+ class OneStep_Cash_Karp_RK45 < Cash_Karp_RK4
15
+ SAFETY = 0.9
16
+ PGROW = -0.2
17
+ PSHRNK = -0.25
18
+ ERRCON = 1.89e-4
19
+
20
+ def integrate_ad_step(y, dydx, x, htry, eps, yscal, derivs)
21
+ h = htry
22
+
23
+ while(true) do
24
+ ytemp, yerr = integrate_fixed_step(y, dydx, x, h, derivs)
25
+ errmax = yerr.collect2(yscal) { |err, scal| (err/scal).abs }.max
26
+ errmax /= eps
27
+ break if errmax <= 1.0
28
+ htemp = SAFETY * h * (errmax ** PSHRNK)
29
+ h = h >= 0.0 ? [htemp, 0.1 * h].max : [htemp, 0.1 * h].min
30
+ xnew = x + h
31
+ raise "Step underflow!" if xnew == x
32
+ end
33
+
34
+ if errmax > ERRCON
35
+ hnext = SAFETY * h * (errmax ** PGROW)
36
+ else
37
+ hnext = 5 * h
38
+ end
39
+
40
+ hdid = h
41
+ x += h
42
+ y = ytemp
43
+
44
+ [x, y, hdid, hnext]
45
+ end
46
+
47
+ end
48
+
49
+ if __FILE__ == $0
50
+
51
+ end
@@ -0,0 +1,9 @@
1
+ module Integrator #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/integrator.rb ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ #
4
+ # Integrator library
5
+ # Copyright (C) 2007 Noah Gibbs
6
+ #
7
+ # This library is in the public domain, and may be redistributed in any
8
+ # way.
9
+ #
10
+
11
+ $:.unshift File.dirname(__FILE__)
12
+
13
+ module Integrator
14
+
15
+ def Integrator_initialize()
16
+ @kmax = 0 # Max steps to save
17
+ @dxsav = 0.01 # Minimum step size to save data
18
+ @xp = nil # Array to save X coords
19
+ @yp = nil # Array to save Y vectors
20
+
21
+ # Stats from most recent integrate call
22
+ @nok = 0
23
+ @nbad = 0
24
+ @count = 0
25
+ end
26
+
27
+ private
28
+
29
+ def set_adaptive_sample_count(count, num_ok)
30
+ @count = count
31
+ @nok = num_ok
32
+ @nbad = count - num_ok
33
+ end
34
+
35
+ public
36
+
37
+ # Set sample logging. Num_max is the maximum number of
38
+ # samples to log, and min_x_save is the minimum distance
39
+ # between x samples to bother to log.
40
+ #
41
+ def set_max_samples(num_max, min_x_save = 0.01)
42
+ @kmax = num_max
43
+ @xp = []
44
+ @yp = []
45
+ @dxsav = min_x_save
46
+ end
47
+
48
+ # Integrate from time t_start to t_end, starting from initial
49
+ # value y0. Use the 'derivs' proc to evaluate derivatives at
50
+ # a given point. Start by taking a step of size step_start,
51
+ # take steps no smaller than step_min, and attempt to keep the
52
+ # error per step down to a maximum of epsilon.
53
+ #
54
+ def integrate(y0, t_start, t_end, derivs, step_start = 0.1,
55
+ step_min = 0.0001, epsilon = 0.01)
56
+ raise "y0 must be a vector!" unless y0.kind_of?(Vector)
57
+ raise "Must give derivative proc!" unless derivs.kind_of?(Proc)
58
+
59
+ adaptive_integrate(y0, t_start, t_end, epsilon,
60
+ step_start, step_min, derivs)
61
+ end
62
+
63
+ end
data/log/debug.log ADDED
File without changes
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/script/txt2html ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'newgem'
6
+ rescue LoadError
7
+ puts "\n\nGenerating the website requires the newgem RubyGem"
8
+ puts "Install: gem install newgem\n\n"
9
+ exit(1)
10
+ end
11
+ require 'redcloth'
12
+ require 'syntax/convertors/html'
13
+ require 'erb'
14
+ require File.dirname(__FILE__) + '/../lib/integrator/version.rb'
15
+
16
+ version = Integrator::VERSION::STRING
17
+ download = 'http://rubyforge.org/projects/integrator'
18
+
19
+ class Fixnum
20
+ def ordinal
21
+ # teens
22
+ return 'th' if (10..19).include?(self % 100)
23
+ # others
24
+ case self % 10
25
+ when 1: return 'st'
26
+ when 2: return 'nd'
27
+ when 3: return 'rd'
28
+ else return 'th'
29
+ end
30
+ end
31
+ end
32
+
33
+ class Time
34
+ def pretty
35
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
36
+ end
37
+ end
38
+
39
+ def convert_syntax(syntax, source)
40
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
41
+ end
42
+
43
+ if ARGV.length >= 1
44
+ src, template = ARGV
45
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
46
+
47
+ else
48
+ puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
49
+ exit!
50
+ end
51
+
52
+ template = ERB.new(File.open(template).read)
53
+
54
+ title = nil
55
+ body = nil
56
+ File.open(src) do |fsrc|
57
+ title_text = fsrc.readline
58
+ body_text = fsrc.read
59
+ syntax_items = []
60
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
61
+ ident = syntax_items.length
62
+ element, syntax, source = $1, $2, $3
63
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
64
+ "syntax-temp-#{ident}"
65
+ }
66
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
67
+ body = RedCloth.new(body_text).to_html
68
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
69
+ end
70
+ stat = File.stat(src)
71
+ created = stat.ctime
72
+ modified = stat.mtime
73
+
74
+ $stdout << template.result(binding)