A better way to write this function?

J

Jason Lillywhite

Here is my attempt at Newton's second law in Ruby:

#c is the drag coefficent
#m is mass
#t is total time elapsed
#dt is time increment
#vi is the starting velocity

G = 9.8

def velocity(c, m, t, dt, vi)
vel = []
t += dt
steps = t/dt

steps.times do
v = vi
vi = v + ( G - c/m*v) * dt
vel << v
end
return vel
end

Is there a better way to do write this function? It seems pretty
straight forward to me so I guess that might be a sign that it is fine
the way it is...?

What if I needed more speed but still wanted it in Ruby?

Thank you!
 
R

Robert Klemme

def velocity(c, m, t, dt, vi)
vel = []
t += dt
steps = t/dt

steps.times do
v = vi
vi = v + ( G - c/m*v) * dt
vel<< v
end
return vel
end

Just a slight improvement

def velocity(c, m, t, dt, vi)
vel = []

(t/dt + 1).times do
vel << vi
vi += ( G - c/m*v) * dt
end

vel
end

You could also do

def velocity(c, m, t, dt, vi)
(t/dt + 1).times do
yield vi
vi += ( G - c/m*v) * dt
end
end

and use it with a block.

Kind regards

robert
 
C

Caleb Clausen

Here is my attempt at Newton's second law in Ruby:

#c is the drag coefficent
#m is mass
#t is total time elapsed
#dt is time increment
#vi is the starting velocity

G = 9.8

def velocity(c, m, t, dt, vi)
vel = []
t += dt
steps = t/dt

steps.times do
v = vi
vi = v + ( G - c/m*v) * dt
vel << v
end
return vel
end

Is there a better way to do write this function? It seems pretty
straight forward to me so I guess that might be a sign that it is fine
the way it is...?

What if I needed more speed but still wanted it in Ruby?

A couple small speedups. c/m never changes while the loop is running,
so compute it once when the loop starts, then reuse the value. And you
could replace the times loop by a while loop for another small
speedup.... but this is never going to be fast in ruby. If you want
fast, use C.
 
J

Joel VanderWerf

Jason said:
What if I needed more speed but still wanted it in Ruby?

Not quite pure ruby, but if it's acceptable to have a C backend, then
you can use redshift. On my computer, if I create 20K of the objects
below, they run (concurrently) at about "real" time, i.e. 1 sec process
time per 1 sec simulation time. Also, it is more accurate than your
integration algorithm, since it uses a higher-order Runge-Kutta
integrator. Plus, there are other features suitable for
discrete/continuous multi-agent simulation (algebraic equations,
discrete state transitions, event synchronization, queues with
pattern-matching like Erlang, dataflow ports like Simulink, link
variables, delay flows, differentiation, interactive shell and
debuggers, ...).

If you want to try this, unpack this tarball:

http://path.berkeley.edu/~vjoel/redshift/redshift-1.3.14.tgz

and put the lib dir on your RUBYLIB. Some of the examples expect gnuplot
to be installed, but otherwise there are no deps.

However, you must be able to build extensions, i.e, have a working C
compiler that is binary compatible with your ruby (gcc, msvc, and
solaris work for me), so that redshift can translate the equations into
C and compile them into ruby extensions. If native gems build on your
system, you should be ok. Note that the first time you run a particular
redshift program, there is a delay while this build happens (the build
goes into a tmp dir under the current dir).

-----

require 'redshift'

class Thing < RedShift::Component
continuous :v, :x
constant :m, :c, :G # per-instance constants

flow do
# "differential" means Runge-Kutta 4th order integration; if
# you replace that word with "euler", it uses forward Euler
# integration, which gives exactly the same results as the
# original poster's example. Usually, RK4 is significantly
# more accurate than Euler.
differential " v' = G - c/m * v "

# just for fun, let's keep track of distance traveled
differential " x' = v "
end
end

world = RedShift::World.new
world.time_step = 0.1

thing = world.create Thing
thing.m = 10
thing.c = 0.01
thing.v = 0
thing.x = 0
thing.G = 9.8

p thing
world.evolve 5.0 do
p thing
end

__END__

Output:

<<Thing 0>: Enter; G = 9.8, c = 0.01, m = 10.0; v = 0.0, x = 0.0>
<<Thing 0>: Enter; G = 9.8, c = 0.01, m = 10.0; v = 0.979951001633293, x
= 0.0489983667075>
<<Thing 0>: Enter; G = 9.8, c = 0.01, m = 10.0; v = 1.95980401306601, x
= 0.195986933986642>
...
...
<<Thing 0>: Enter; G = 9.8, c = 0.01, m = 10.0; v = 47.9025429248678, x
= 117.457075132208>
<<Thing 0>: Enter; G = 9.8, c = 0.01, m = 10.0; v = 48.8777039117133, x
= 122.296088286671>
 
C

Colin Bartlett

[Note: parts of this message were removed to make it a legal post.]

On Fri, May 28, 2010 at 4:20 PM, Jason Lillywhite <
Here is my attempt at Newton's second law in Ruby:
...
What if I needed more speed but still wanted it in Ruby?

Using C for the "fast" bits has been suggested.

As an alternative to C for speed, I've found that using JRuby with parts of
the code as Java functions works well.

Perhaps I ought to add that I've never tried compiling a C program and
integrating it with Ruby, because of my lack of knowledge of the finer - and
not so finer! - points of C compilation and integration with Ruby. But I've
found writing and compiling parts of the code that need to be fast fairly
easy in Java, and integration of Java with JRuby is also fairly
straightforward.
 
B

brabuhr

On Fri, May 28, 2010 at 4:20 PM, Jason Lillywhite <


Using C for the "fast" bits has been suggested.

As an alternative to C for speed, I've found that using JRuby with parts = of
the code as Java functions works well.

I took a quick shot w/ duby-inline.

Host:
2.6.32-22-generic #33-Ubuntu SMP Wed Apr 28 13:27:30 UTC 2010 i686 GNU/Linu=
x
Intel(R) Pentium(R) M processor 1.60GHz

Benchmark block:
100000.times{ velocity(0.5, 21.6, 600, 10, 0.0) }

"Original" code (see note below):

ruby 1.8.7 (2010-01-10 patchlevel 249) [i486-linux]
user system total real
18.210000 0.040000 18.250000 ( 21.596111)

jruby 1.5.0 (ruby 1.8.7 patchlevel 249) (2010-05-12 6769999) (OpenJDK
Client VM 1.6.0_18) [i386-java]
user system total real
4.525000 0.000000 4.525000 ( 4.525000)

jruby 1.5.0 (ruby 1.8.7 patchlevel 249) (2010-05-12 6769999) (OpenJDK
Server VM 1.6.0_18) [i386-java]
user system total real
3.651000 0.000000 3.651000 ( 3.651000)

Duby code:

jruby 1.5.0 (ruby 1.8.7 patchlevel 249) (2010-05-12 6769999) (OpenJDK
Client VM 1.6.0_18) [i386-java]
0.415000 0.000000 0.415000 ( 0.415000)

jruby 1.5.0 (ruby 1.8.7 patchlevel 249) (2010-05-12 6769999) (OpenJDK
Server VM 1.6.0_18) [i386-java]
0.208000 0.000000 0.208000 ( 0.208000)


I had to cheat a little bit and removed the array and the block to
make it Duby-compatible:

G =3D 9.8

class Newton
def rb_velocity(c, m, t, dt, vi)
t +=3D dt
steps =3D t/dt

i =3D 0
while i < steps
v =3D vi
vi =3D v + ( G - c/m*v) * dt
i +=3D 1
end

vi
end
end

And, the duby version:

require 'inline'
require 'duby_inline'

G =3D 9.8

class Newton
inline :Duby do |builder|
builder.duby "
def db_velocity(c:double, m:double, t:int, dt:int, vi:double)
t +=3D dt
steps =3D t/dt

i =3D 0
while i < steps
v =3D vi
vi =3D v + ( #{G} - c/m*v) * dt
i +=3D 1
end

vi
end
"
end
end
 
J

Joel VanderWerf

Joel said:
Jason Lillywhite wrote: ...

Yes, that's the same thing (but there is no formal release on that site).

It's a gem now:

gem install redshift
 
J

Jason Lillywhite

unknown said:
Duby code:
...snip

I'm sorry, can you tell me what Duby is? I tried looking it up and found
nothing. I'm familiar with JRuby but not Duby.

Thank you.
 
B

brabuhr

I'm sorry, can you tell me what Duby is? I tried looking it up and found
nothing. I'm familiar with JRuby but not Duby.

Thank you.

Duby is a Ruby-like static-typed language with local type inference
and not many bells and whistles. The primary motivation was to have a
Ruby-like language to implement parts of JRuby. Duby looks and feels
mostly like Ruby but compiles to tight JVM bytecode on par with what
you'd get from compiled Java.

def fib(a => :fixnum)
if a < 2
a
else
fib(a - 1) + fib(a - 2)
end
end

puts fib(45)

http://blog.headius.com/2008/03/duby-type-inferred-ruby-like-jvm.html
http://blog.headius.com/2008/08/duby-update.html
http://www.infoq.com/presentations/nutter-jruby-duby-juby
http://blog.headius.com/2009/08/introducing-surinx.html
 
J

Joel VanderWerf

I took a quick shot w/ duby-inline. ...
ruby 1.8.7 (2010-01-10 patchlevel 249) [i486-linux]
user system total real
18.210000 0.040000 18.250000 ( 21.596111) ...
jruby 1.5.0 (ruby 1.8.7 patchlevel 249) (2010-05-12 6769999) (OpenJDK
Server VM 1.6.0_18) [i386-java]
0.208000 0.000000 0.208000 ( 0.208000)

That is a very impressive speed-up!

I see a more modest improvement going from the same original code (on
different hardware of course) to a version written in redshift. Of
course, redshift is using a more accurate integration algorithm (with 4
substeps instead of 1). Plus there are costs for the availability of
other features that are not being used in this example (e.g., a layer of
function pointers so that variables can switch to different equations
when a discrete state change occurs).

$ ruby bench-rb.rb
user system total real
evolve 6s 0.710000 0.160000 0.870000 ( 0.881276)
evolve 600s 15.320000 1.010000 16.330000 ( 16.366811)

$ ruby bench-rs.rb
user system total real
evolve 6s 0.020000 0.000000 0.020000 ( 0.025213)
evolve 600s 1.170000 0.000000 1.170000 ( 1.178752)

$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux]

Here are the sources:

$ cat bench-rb.rb
G = 9.8

def velocity(c, m, t, dt, vi)
t += dt
steps = t/dt

i = 0
while i < steps
v = vi
vi = v + ( G - c/m*v) * dt
i += 1
end

vi
end

require 'benchmark'
Benchmark.bm(12) do |b|
b.report("evolve 6s") do
100000.times do
velocity(0.5, 21.6, 6.0, 10.0, 0.0)
# Note: I changed the t and dt to floats for a more direct
# comparison with redshift, which uses floats for time values
end
end
b.report("evolve 600s") do
100000.times do
velocity(0.5, 21.6, 600.0, 10.0, 0.0)
end
end
end

$ cat bench-rs.rb
require 'redshift'

class Thing < RedShift::Component
continuous :v
constant :m, :c, :G

flow do
differential " v' = G - c/m * v "
end
end

def make_world(n)
RedShift::World.new do |world|
world.time_step = 10.0
n.times do
world.create Thing do |thing|
thing.m = 21.6
thing.c = 0.5
thing.v = 0
thing.G = 9.8
end
end
end
end

require 'benchmark'
Benchmark.bm(12) do |b|
n = 1000
reps = 100
# Note: instead of 100000 sequential runs, this benchmark is doing
# 100 sequential runs of 1000 parallel instances of the same
# integration problem, which is more typical of the conditions
# redshift is optimized for

world = make_world(n)
b.report("evolve 6s") do
reps.times do
world.evolve 6.0
end
end

world = make_world(n)
b.report("evolve 600s") do
reps.times do
world.evolve 600.0
end
end
end
 
B

Brian Candler

Jason said:
G = 9.8

def velocity(c, m, t, dt, vi)
vel = []
t += dt
steps = t/dt

steps.times do
v = vi
vi = v + ( G - c/m*v) * dt
vel << v
end
return vel
end

Is there a better way to do write this function?

You can make it a one-liner using range and map:

def velocity(c, m, t, dt, v)
[v] + (0...t/dt).map { v += (G - c/m*v) * dt }
end

This version also works with float values of t and dt, which yours
rejects because there is no Float#times.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,153
Members
46,701
Latest member
XavierQ83

Latest Threads

Top