[QUIZ] SimFrost (#117)

R

Ruben Medellin

James said:
The three rules of Ruby Quiz:

Woah, this quiz was very entertaining. I enjoyed a lot doing it, and
still enjoy watching it every time :D

As the console version wouldn't let me be happy, I tried to do it using
OpenGL. I got it at the end, although it's pretty slow (runs at decent
speed for size of <200*200, obviously the greater values the slowest),
but I'm happy with it for being my first try using GL.

I'll probably spend another little time tuning it up (as I stated again
that premature optimization is evil), and perhaps designing the 3D view
someone suggested :O.

Thanks again for the great quizes!
____________

# Quiz 117 : SimFrost
# Ruben Medellin <[email protected]>
# Usage> ruby quiz117.rb width height vapor_density

#Based on OpenGL
require 'opengl'
require 'glut'

# Each pixel represents an element.
class Element

attr_accessor :element

def initialize(element)
@element = element
end

#Just a change of state. Don't forget to decrement the vapor number.
def freeze
if @element == :vapor
$VAPOR -= 1
@element = :ice
end
end
end

# Main class
class Freeze_Simulator

#Standard initializer. It prepares the windows to be called.
def initialize

$WIDTH = ARGV[0] && ARGV[0].to_i || 100
$HEIGHT = ARGV[1] && ARGV[1].to_i || 100

$WIDTH += 1 if $WIDTH % 2 != 0
$HEIGHT += 1 if $HEIGHT % 2 != 0

$DENSITY = ARGV[2] && ARGV[2].to_i || 30

$VAPOR = 0

# We create a matrix, assigning randomly (according to the
# percentage) the density of vapor. Vacuum is represented by nil.
$GRID = Array.new($HEIGHT) do
Array.new($WIDTH) do
if rand(100) > $DENSITY
nil
else
# We need this counter if we want a nice quick
# checker for all_frozen? method
$VAPOR += 1
Element.new:)vapor)
end
end
end

#We set the center to be frozen
($GRID[$HEIGHT/2][$WIDTH/2] = Element.new:)vapor)).freeze

$TICK_COUNT = 0

$PIXELS = []

#Standard GL methods
GLUT.Init
GLUT.InitDisplayMode(GLUT::SINGLE)
GLUT.InitWindowSize($WIDTH, $HEIGHT)
GLUT.InitWindowPosition(100, 100)
GLUT.CreateWindow('SimFrost : Quiz #117 - by CHubas')
GL.ShadeModel(GL::FLAT)

make_matrix
GLUT.DisplayFunc(method:)display).to_proc)
GLUT.KeyboardFunc(Proc.new{|k, x, y| exit if k == 27})
GLUT.ReshapeFunc(method:)reshape).to_proc)

# IdleFunc takes a proc object and calls it continously whenever it
can.
GLUT.IdleFunc(method:)tick).to_proc)
end

# Here we create the pixel information.
# Open GL takes an array of integers and splits it in groups of three
# that represent one color component each.
def make_matrix
for i in 0..$HEIGHT-1
for j in 0..$WIDTH-1
index = (i * $WIDTH + j) * 3
if particle = $GRID[j]
case particle.element
when :vapor
# A blue-ish color
$PIXELS[index] = 50
$PIXELS[index+1] = 100
$PIXELS[index+2] = 255
when :ice
# White ice
$PIXELS[index] = 255
$PIXELS[index+1] = 255
$PIXELS[index+2] = 255
end
else
# Black
$PIXELS[index] = 0
$PIXELS[index+1] = 0
$PIXELS[index+2] = 0
end
end
end
end

# Some basic window behavior
def reshape(width, height)
GL.PixelZoom(width / $WIDTH.to_f, height / $HEIGHT.to_f)
display
end

# Draws the pixel bitmap
def display
GL.DrawPixels($WIDTH, $HEIGHT, GL::RGB, GL::UNSIGNED_BYTE,
$PIXELS.pack("C*"))
GL.Flush
end

# We split the board into 2*2 squares with this method
def each_square( tick_number )
start = 0
w_end = $WIDTH
h_end = $HEIGHT

if tick_number % 2 != 0 #odd
start -= 1
w_end -= 1
h_end -= 1
end

(start...h_end).step(2) do |row|
(start...w_end).step(2) do |column|
s = yield *get_square_at(row, column)
set_square_at(row, column, s)
end
end

end

# Checks for each 2*2 square and does the proper transformation
def tick
each_square( ($TICK_COUNT += 1) ) do |*square|
if square.any?{|e| e != nil && e.element == :ice}
for e in square
e.freeze
end
square
else
rotate(square)
end
end

# Having modified the matrix, now we have to rebuild the pixel map
make_matrix
GLUT.PostRedisplay
GLUT.SwapBuffers

#Stop doing this if everything is frozen already
GLUT.IdleFunc(nil) if all_frozen?
end

# Some dirty methods
def get_square_at(row, column)
[$GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]]
end

def set_square_at(row, column, new_square)
$GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]
= new_square
end

# Rotates elements in
# | 0 1 |
# | 2 3 |
def rotate(square)
if rand(2) == 0
square.values_at(1,3,0,2)
else
square.values_at(2,0,3,1)
end
end

# Validates if there is any vapor particle
def all_frozen?
if $VAPOR > 0
return false
else
puts "Welcome to the ice age!"
puts "All frozen in #{$TICK_COUNT} thicks"
return true
end
end

# Starts the main loop
def start
GLUT.MainLoop
end

end

#Let the fun begin
Freeze_Simulator.new.start

________

Now I wonder how to make a movie :O
 
R

Robert Dober

James said:
The three rules of Ruby Quiz:

Woah, this quiz was very entertaining. I enjoyed a lot doing it, and
still enjoy watching it every time :D

As the console version wouldn't let me be happy, I tried to do it using
OpenGL. I got it at the end, although it's pretty slow (runs at decent
speed for size of <200*200, obviously the greater values the slowest),
but I'm happy with it for being my first try using GL.

I'll probably spend another little time tuning it up (as I stated again
that premature optimization is evil), and perhaps designing the 3D view
someone suggested :O.

Thanks again for the great quizes!
____________

# Quiz 117 : SimFrost
# Ruben Medellin <[email protected]>
# Usage> ruby quiz117.rb width height vapor_density

#Based on OpenGL
require 'opengl'
require 'glut'

# Each pixel represents an element.
class Element

attr_accessor :element

def initialize(element)
@element = element
end

#Just a change of state. Don't forget to decrement the vapor number.
def freeze
if @element == :vapor
$VAPOR -= 1
@element = :ice
end
end
end

# Main class
class Freeze_Simulator

#Standard initializer. It prepares the windows to be called.
def initialize

$WIDTH = ARGV[0] && ARGV[0].to_i || 100
$HEIGHT = ARGV[1] && ARGV[1].to_i || 100

$WIDTH += 1 if $WIDTH % 2 != 0
$HEIGHT += 1 if $HEIGHT % 2 != 0

$DENSITY = ARGV[2] && ARGV[2].to_i || 30

$VAPOR = 0

# We create a matrix, assigning randomly (according to the
# percentage) the density of vapor. Vacuum is represented by nil.
$GRID = Array.new($HEIGHT) do
Array.new($WIDTH) do
if rand(100) > $DENSITY
nil
else
# We need this counter if we want a nice quick
# checker for all_frozen? method
$VAPOR += 1
Element.new:)vapor)
end
end
end

#We set the center to be frozen
($GRID[$HEIGHT/2][$WIDTH/2] = Element.new:)vapor)).freeze

$TICK_COUNT = 0

$PIXELS = []

#Standard GL methods
GLUT.Init
GLUT.InitDisplayMode(GLUT::SINGLE)
GLUT.InitWindowSize($WIDTH, $HEIGHT)
GLUT.InitWindowPosition(100, 100)
GLUT.CreateWindow('SimFrost : Quiz #117 - by CHubas')
GL.ShadeModel(GL::FLAT)

make_matrix
GLUT.DisplayFunc(method:)display).to_proc)
GLUT.KeyboardFunc(Proc.new{|k, x, y| exit if k == 27})
GLUT.ReshapeFunc(method:)reshape).to_proc)

# IdleFunc takes a proc object and calls it continously whenever it
can.
GLUT.IdleFunc(method:)tick).to_proc)
end

# Here we create the pixel information.
# Open GL takes an array of integers and splits it in groups of three
# that represent one color component each.
def make_matrix
for i in 0..$HEIGHT-1
for j in 0..$WIDTH-1
index = (i * $WIDTH + j) * 3
if particle = $GRID[j]
case particle.element
when :vapor
# A blue-ish color
$PIXELS[index] = 50
$PIXELS[index+1] = 100
$PIXELS[index+2] = 255
when :ice
# White ice
$PIXELS[index] = 255
$PIXELS[index+1] = 255
$PIXELS[index+2] = 255
end
else
# Black
$PIXELS[index] = 0
$PIXELS[index+1] = 0
$PIXELS[index+2] = 0
end
end
end
end

# Some basic window behavior
def reshape(width, height)
GL.PixelZoom(width / $WIDTH.to_f, height / $HEIGHT.to_f)
display
end

# Draws the pixel bitmap
def display
GL.DrawPixels($WIDTH, $HEIGHT, GL::RGB, GL::UNSIGNED_BYTE,
$PIXELS.pack("C*"))
GL.Flush
end

# We split the board into 2*2 squares with this method
def each_square( tick_number )
start = 0
w_end = $WIDTH
h_end = $HEIGHT

if tick_number % 2 != 0 #odd
start -= 1
w_end -= 1
h_end -= 1
end

(start...h_end).step(2) do |row|
(start...w_end).step(2) do |column|
s = yield *get_square_at(row, column)
set_square_at(row, column, s)
end
end

end

# Checks for each 2*2 square and does the proper transformation
def tick
each_square( ($TICK_COUNT += 1) ) do |*square|
if square.any?{|e| e != nil && e.element == :ice}
for e in square
e.freeze
end
square
else
rotate(square)
end
end

# Having modified the matrix, now we have to rebuild the pixel map
make_matrix
GLUT.PostRedisplay
GLUT.SwapBuffers

#Stop doing this if everything is frozen already
GLUT.IdleFunc(nil) if all_frozen?
end

# Some dirty methods
def get_square_at(row, column)
[$GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]]
end

def set_square_at(row, column, new_square)
$GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]
= new_square
end

# Rotates elements in
# | 0 1 |
# | 2 3 |
def rotate(square)
if rand(2) == 0
square.values_at(1,3,0,2)
else
square.values_at(2,0,3,1)
end
end

# Validates if there is any vapor particle
def all_frozen?
if $VAPOR > 0
return false
else
puts "Welcome to the ice age!"
puts "All frozen in #{$TICK_COUNT} thicks"
return true
end
end

# Starts the main loop
def start
GLUT.MainLoop
end

end

#Let the fun begin
Freeze_Simulator.new.start

________

Now I wonder how to make a movie :O

I eventually used NetPBM on Linux but the mpegs get quite big it seems
quite difficult to tune. I have a nice 512**2 movie with the initial
freezer up left but it got >30MB brrr.
Robert
 
C

Christoffer Lernö

What's a "small grid" and what's "quite a bit"? ;)

For 10x10 with 10% vapour, the number of particles typically range =20
between 5 and 15, that's between 50% and 150% of the desired amount. =20
That's quite a bit, since the patterns with 15 vapour particles are =20
quite different from one with 5.

The larger the grid, the more this value evens out (of course).

I'm not saying that this is necessarily a flaw in the solutions, I =20
just thought it was an interesting problem - randomly mixing elements =20=

in a two-dimensional grid.


Christoffer
 
J

James Edward Gray II

For 10x10 with 10% vapour, the number of particles typically range =20
between 5 and 15, that's between 50% and 150% of the desired =20
amount. That's quite a bit, since the patterns with 15 vapour =20
particles are quite different from one with 5.

Sure, but a 10x10 isn't a sure interesting simulation. ;)
I'm not saying that this is necessarily a flaw in the solutions, I =20
just thought it was an interesting problem - randomly mixing =20
elements in a two-dimensional grid.

Yes, it is interesting.

James Edward Gray II=
 
J

James Edward Gray II

Any suggestions for a complete beginner in generating graphics for
a more
efficient way of doing this sim graphically? RMagick is real nice,
but it's
not doing too well generating gif with a 320x240 grid.

Did you take a look at my solution that outputs PPM graphics? PPM is
a very easy graphic format, so I think it's great for beginners.

James Edward Gray II
 
J

James Edward Gray II

I was hoping to write PPMs a while ago, but OS X's Preview doesn't
seem
to support it.

Yeah, I don't think preview does. Graphic Converter does though.
It's what I used to make the movie. I bet you can also get netpbm
installed on OS X, then you can just shell out to those programs to
convert the images or build a movie.

James Edward Gray II
 
J

James Edward Gray II

Did some testing, and came out with this...
BLUE = [ 0, 255, 0].pack("C*")
GREEN = [ 255, 0, 0].pack("C*")
RED = [ 0, 0, 255].pack("C*")

is it just in my PC?

Weird, I can't think how that is possible...

James Edward Gray II
 
J

James Edward Gray II

seems like the mailing list doesn't like ppm attachments, ate my last
message...

The list has a size limit. Try zipping the file.
Previous message said:

Hey, James, I ran your solution in my PC (running win2k), and the
ppm came
out red instead of blue...

Weird. I don't have any good guesses on that one... :(

James Edward Gray II
 
R

Robert Dober

<snip>
Yeah, I don't think preview does. Graphic Converter does though.
It's what I used to make the movie. I bet you can also get netpbm
installed on OS X, then you can just shell out to those programs to
convert the images or build a movie.

James Edward Gray II

Talking about netpbm, I used it from Linux and I had no problem to
create an mpeg from 705 frames (512x512) only that err... it is about
50 times too big.
No way to post this to the list, I continue to try making it smaller -
this is my first attempt of video compression.

Would you be interested in it for the site maybe, and if so what size
is acceptable?
~ 10MB?

So if someone wants the configuration file for ppmtompeg I can post
it(1), maybe we will get some help to make the files smaller too?
Cheers
Robert

(1) ASAI am home again ;)
 
R

Robert Dober

I'll try it out on the Linux side as soon as this 500x500 sim's done :)
My 512x512 sim took 2 hours :( but the ppm files will stay for
eternity so runtime was about 0 ;).
 
J

James Edward Gray II

Talking about netpbm, I used it from Linux and I had no problem to
create an mpeg from 705 frames (512x512) only that err... it is about
50 times too big.
No way to post this to the list, I continue to try making it smaller -
this is my first attempt of video compression.

Would you be interested in it for the site maybe, and if so what size
is acceptable?

I'm going to pass just because I already have one movie up there and
I'm sure that's doing enough damage to my alloted bandwidth already. ;)

James Edward Gray II
 
H

Harrison Reiser

Whoop dee do, I spent half of yesterday setting up ImageMagick and
RMagick and now I learn that PPMs are better. ;) Ah well.
 
J

James Edward Gray II

Whoop dee do, I spent half of yesterday setting up ImageMagick and
RMagick and now I learn that PPMs are better. ;) Ah well.

Oh not "better." More just a poor man's RMagick. ;)

Well, for *very* simple graphics at least. RMagick does a LOT more.

James Edward Gray II
 
J

James Edward Gray II

Sorry for the noise ppl, but this was kinda bugging me...
On the color issues of the ppm, windows was sneaking a CR at the *
P6*80 25 255*

As an interesting aside, Graphic Converter reports the image as
broken. Did I mention that I love that program? ;)

James Edward Gray II
 
R

rretzbach

Woah, this quiz was very entertaining. I enjoyed a lot doing it, and
still enjoy watching it every time :D

As the console version wouldn't let me be happy, I tried to do it using
OpenGL. I got it at the end, although it's pretty slow (runs at decent
speed for size of <200*200, obviously the greater values the slowest),
but I'm happy with it for being my first try using GL.

Nice that someone did it with OpenGL.
I just installed ruby-opengl to try your solution.
But gem always want to make me cry.

Now require 'opengl' is no longer an option
it must be
require 'rubygems'
gem 'ruby-opengl'
Thanks for that Mr. Gem.

I am on Ubuntu, but i remember having similar probs on Windows, maybe
the exact opposite :\

For now I wrote my own opengl.rb and put it in my loadpath.
Maybe someone needs it too :>

--->8---
require 'rubygems'
gem 'ruby-opengl'

require 'glut'
require 'gl'

%w(glut gl).each do |modul|

eval <<-CODE
#{modul.upcase} = #{modul.capitalize}
module #{modul.capitalize}
def self.method_missing sym, *args
send(("#{modul.downcase}" + sym.to_s).to_sym, *args)
end
def self.const_missing sym
const_set(sym, const_get("#{modul.upcase}_" + sym.to_s))
end
end
CODE

end
 
C

CHubas

Nice that someone did it with OpenGL.
I just installed ruby-opengl to try your solution.
But gem always want to make me cry.

Now require 'opengl' is no longer an option
it must be
require 'rubygems'
gem 'ruby-opengl'
Thanks for that Mr. Gem.

I am on Ubuntu, but i remember having similar probs on Windows, maybe
the exact opposite :\

For now I wrote my own opengl.rb and put it in my loadpath.
Maybe someone needs it too :>

--->8---
require 'rubygems'
gem 'ruby-opengl'

require 'glut'
require 'gl'

%w(glut gl).each do |modul|

eval <<-CODE
#{modul.upcase} = #{modul.capitalize}
module #{modul.capitalize}
def self.method_missing sym, *args
send(("#{modul.downcase}" + sym.to_s).to_sym, *args)
end
def self.const_missing sym
const_set(sym, const_get("#{modul.upcase}_" + sym.to_s))
end
end
CODE

end

=)
Actually, I did it Windows, since I was working on it when I read the
quiz. However, on my Ubuntu, Mr. Gems wouldn't actually let me work
(that fed by the fact I did a mess on my dependences, and hadn't
cleaned it up D: )

I'll try it, thanks :D
 
R

Ruben Medellin

:)
As promised, I tuned up my code for OpenGL, and runs it faster, allowing
bigger boards.

Here's the code:

http://pastie.caboo.se/46744

And... playing a little more with OpenGL, I managed to do a 3D
simulation. The bad thing is that I'm a completely newb into it, so it
doesn't look like expected, mainly because I'm having troubles with the
viewport, shadows, angles, lighting and all that. This quiz is a good
excuse for me to learn :D

I'll throw also the code I experimented with, if someone'd like to check
it and play with it, it's basically the same code expanded to a 3D
array. Of course, it would be nicer if someone experienced with GL could
make it look better :D

http://pastie.caboo.se/46751

I'll show a couple of images resulting from it.

http://img98.imageshack.us/img98/4487/simfrost3d3cv6.gif
http://img368.imageshack.us/img368/1899/simfrost3d4lg2.gif

Rubén

(P.S. Sorry for the double posting)
 
J

Josef 'Jupp' Schugt

--------------enig98EF0C3220500747A59F0A5A
Content-Type: text/plain; charset=ISO-8859-15
Content-Transfer-Encoding: quoted-printable

Hi!

* Ruby Quiz, 09.03.2007 13:58:
The goal is to create a simulation of frost.

My aim was not to solve the task in a very object-oriened way or to
have a shiny output. My major objectives were

* Writing a Ruby program that can easily be ported to C, porting it
and then compare the performance. It turned out that the C
implementation is faster by a factor of about 100.

* Finding a simple way of animating the produced data without
having to implement a GUI.

For the implementation see source below, let's turn to animation:

I use numbered PGM files as output format. For tick 0 (initial state
tick_00000.pgm is written, for tick 1 tick_00001.pgm and so forth.
For large simulations - I ran a C 1280x1024 C simulation with 25%
vapor that took about 7 minutes (with the bottleneck being the
Journaling File System) that took 3472 ticks meaning 3473 output
frames - it can be a good idea not to display all of the frames but
only every 10th or so. This goal can easily achieved by not looking
at all frames but only at those matching "tick_*0.pgm". For every
20th one could use "tick_*[02468]*.pgm" and so on.

One way of animating the output is using "convert" to create an
animated gif in the following manner:

convert -delay 0 -loop 0 tick_*.pgm simfrost.gif

Beware that this can require quite a lot of RAM!

A less demanding way of animtin the output is using an appropriate
display program. Personally I perfer qiv. To use this program to
animate the output all one has to do is issue

qiv -s -d 0 tick_*pgm

Where '-s' orders qiv to display the images as a slideshow and '-d'
provides a delay in seconds between the individual images, for I was
using the program for 1280x1024 images I set this delay to 0.

qiv is available at http://www.klografx.net/qiv/

The second major advantage of using PGM or PBM or PGM besides the
simplicity of output is that they can be converted to virtually any
other graphics format because they are the generic formats used by
the netpbm tools, see http://netpbm.sourceforge.net/

-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=
=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-
Follows Ruby implementation
-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=
=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-
#!/usr/bin/ruby -w

#####################################################################
# Ruby Quiz 117, SimFrost
#
# The simulation uses an array of integers.
#
# It seems to make sense to use
#
# 0 to represent vacuum
# 1 to represent vapor
# 2 to represent ice
#
# Note that your terminal emulation should support ANSI escape
# sequences
#
#####################################################################

#####################################################################
# Integer#even - see if Integer is even
#####################################################################

class Integer
def even?
self/2*2 =3D=3D self
end
end

#####################################################################
# cls - clear screen
#####################################################################

def cls
print "\e[2J"
end

#####################################################################
# home - move cursor to home position
#####################################################################

def home
print "\e[1;1H"
end

#####################################################################
# Get even positive number
#####################################################################

def get_even_positive(desc)
n =3D 0
until n > 0 && n.even?
print "Please enter #{desc} (must be even and positive): "
n =3D gets.to_i
end
return n
end

#####################################################################
#
# Read probability
#
# Input is probability in percent, return value is probability
#
#####################################################################

def get_probability(desc)
p =3D -1.0
while p < 0.0 or p > 100.0
print "Please enter probability for #{desc} (in %, float): "
p =3D gets.to_f
end
return p / 100.0
end

#####################################################################
#
# Read settings
#
#####################################################################

def get_settings
okay =3D "no"
while okay !=3D "yes"
cls
cols =3D get_even_positive("number of columns")
rows =3D get_even_positive("number of rows")
prob =3D get_probability("vapor")
puts <<-EOF
You want:
\t#{cols}\tcolums
\t#{rows}\trows
\t#{prob*100.0}\tas the initial probabilty for vapor in percent
IS THAT CORRECT? If so please answer with: yes
EOF
okay =3D gets.chomp
puts "Please re-enter data." unless okay =3D=3D "yes"
end
return { "cols" =3D> cols, "rows" =3D> rows, "prob" =3D> prob }
end

#####################################################################
#
# generate initial state for simulation
#
#####################################################################

def initial_state(cols, rows, prob)
a =3D
Array.new(rows) do |row|
Array.new(cols) do |elem|
rand < prob ? 1 : 0
end
end
a[rows/2][cols/2] =3D 2
return a
end

#####################################################################
#
# output current simulation state
#
#####################################################################

def output_state(state, tick)
home
puts "Simulation tick #{tick}"
filename =3D "tick_#{'%05d' % tick}.pgm"
File.open(filename, 'w') do |file|
file.puts <<-EOF
P2
# #{filename}
#{state.first.length} #{state.length}
2
EOF
state.each do |row|
row.each do |elem|
file.puts elem.to_s
end
end
end
end

#####################################################################
# see if state is frozen out (i.e. no more vapor is present)
#####################################################################

class Array
def frozen_out?
not self.flatten.member?(1)
end
end

#####################################################################
# the simulation itself
#####################################################################

settings =3D get_settings
cols =3D settings["cols"],
rows =3D settings["rows"],
prob =3D settings["prob"]
state =3D initial_state(cols, rows, prob)
tick =3D 0
cls
while true
output_state(state, tick)
break if state.frozen_out?
tick +=3D 1
offset =3D (tick + 1) % 2
i =3D offset
while i < rows
i1 =3D (i + 1) % rows
j =3D offset
while j < cols
j1 =3D (j + 1) % cols
if [ state[j],
state[j1],
state[i1][j],
state[i1][j1] ].member?(2)
state[j] =3D 2 if state[j] =3D=3D 1
state[j1] =3D 2 if state[j1] =3D=3D 1
state[i1][j] =3D 2 if state[i1][j] =3D=3D 1
state[i1][j1] =3D 2 if state[i1][j1] =3D=3D 1
else
if rand < 0.5
state[j], state[j1], state[i1][j], state[i1][j1] =3D
state[j1], state[i1][j1], state[j], state[i1][j]
else
state[j], state[j1], state[i1][j], state[i1][j1] =3D
state[i1][j], state[j], state[i1][j1], state[j1]
end
end
j +=3D 2
end
i +=3D 2
end
end

-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=
=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-
Follows C implementation
-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=
=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void cls(void)
{
printf("\033[2J");
}

void home(void)
{
printf("\033[1;1H");
}

int get_even_positive(char* desc)
{
int n =3D 0;
char s[21];
while( n <=3D 0 || n/2*2 !=3D n)
{
printf("Please enter %s (must be even and positive): ", desc);
scanf("%20s", s);
n =3D atoi(s);
}
return n;
}

double get_probability(char* desc)
{
double p =3D -1.0;
char s[21];
while (p < 0.0 || p > 100.0)
{
printf("Please enter probability for %s (in percent, float): ",
desc);
scanf("%20s", s);
p =3D atof(s);
}
return p / 100.0;
}

int **initialize_state(int cols, int rows, double prob)
{
int i;
int j;
int **a;

a =3D (int **) calloc(rows, sizeof(int *));
for (i =3D 0; i < rows; i++)
{
a =3D (int *) calloc(cols, sizeof(int));
}
for (i =3D 0; i < rows; i++)
{
for (j =3D 0; j < cols; j++)
{
a[j] =3D (rand() < RAND_MAX * prob) ? 1 : 0;
}
}
a[rows/2][cols/2] =3D 2;
return a;
}

void display_state(int **state, int tick, int cols, int rows)
{
int i;
int j;
char filename[15];
FILE *file;

home();
printf("Simulation tick %d\n", tick);
sprintf(filename, "tick_%05d.pgm", tick);
file =3D fopen(filename, "w");
fprintf(file, "P2\n");
fprintf(file, "# %s\n", filename);
fprintf(file, "%d %d\n", cols, rows);
fprintf(file, "2/n");
for (i =3D 0; i < rows; i++)
{
for (j =3D 0; j < cols; j++)
{
putc("012"[state[j]], file);
putc('\n', file);
}
}
fclose(file);
}

int frozen_out(int **state, int cols, int rows)
{
int i;
int j;

for (i =3D 0; i < rows; i++)
{
for (j =3D 0; j < cols; j++)
{
if (state[j] =3D=3D 1)
{
return 0;
}
}
}
return 1;
}

int main(void)
{
int okay =3D 0;
int tick =3D 0;
int offset;
int cols;
int rows;
int i, i1;
int j, j1;
int h00, h01, h10, h11;
double prob;
char s[21];
int **state;

while (!okay)
{
cls();
cols =3D get_even_positive("number of columns");
rows =3D get_even_positive("number of rows");
prob =3D get_probability("vapor");
printf("You want:\n");
printf("\t%d\tcolums\n", cols);
printf("\t%d\trows\n", rows);
printf("\t%f\tas the initial probabilty for vapor in percent\n",
prob * 100.0);
printf("IS THAT CORRECT? If so please answer with: yes\n");
scanf("%20s", s);
okay =3D !strcmp(s, "yes");
if (!okay)
{
puts("Please re-enter data.");
}
}
state =3D initialize_state(cols, rows, prob);
cls();
while(1)
{
display_state(state, tick, cols, rows);
if (frozen_out(state, cols, rows))
{
return 0;
}
offset =3D (tick++ + 1) % 2;
for (i =3D offset; i < rows; i +=3D 2)
{
i1 =3D (i + 1) % rows;
for (j =3D offset; j < cols; j +=3D 2)
{
j1 =3D (j + 1) % cols;
if (state[j] =3D=3D 2 ||
state[j1] =3D=3D 2 ||
state[i1][j] =3D=3D 2 ||
state[i1][j1] =3D=3D 2)
{
if (state[j] =3D=3D 1) state[j] =3D 2;
if (state[j1] =3D=3D 1) state[j1] =3D 2;
if (state[i1][j] =3D=3D 1) state[i1][j] =3D 2;
if (state[i1][j1] =3D=3D 1) state[i1][j1] =3D 2;
}
else
{
h00 =3D state[j];
h01 =3D state[j1];
h10 =3D state[i1][j];
h11 =3D state[i1][j1];
if (rand() < RAND_MAX/2)
{
state[j] =3D h01;
state[j1] =3D h11;
state[i1][j] =3D h00;
state[i1][j1] =3D h10;
}
else
{
state[j] =3D h10;
state[j1] =3D h00;
state[i1][j] =3D h11;
state[i1][j1] =3D h01;
}
}
}
}
}
return 0;
}
-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=
=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-

Even if you've never seen C before you ought to be able to understand
the C implementation by comparing it to the Ruby one.

Josef 'Jupp' Schugt
--=20
Blog available at http://www.mynetcologne.de/~nc-schugtjo/blog/
PGP key with id 6CC6574F available at http://wwwkeys.de.pgp.net/


--------------enig98EF0C3220500747A59F0A5A
Content-Type: application/pgp-signature; name="signature.asc"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature.asc"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQFF+UvDrhv7B2zGV08RAoJbAJ9WoquZDUpOXFnhHYmU/G0MCUHR6gCfThnV
GMBR6Xw3jqEv5iskhLxCgsE=
=M/L9
-----END PGP SIGNATURE-----

--------------enig98EF0C3220500747A59F0A5A--
 
S

Simon Kröger

Hi Jupp
settings = get_settings
cols = settings["cols"],
rows = settings["rows"],
prob = settings["prob"]

this isn't realy what you wanted, right?

cheers

Simon
 
S

Simon Kröger

Hi again Jupp,

i don't want to start a flamewar here, trust me.
I admit that C is faster than ruby in all situations
that might matter (except developing speed).

Nevertheless some comments to your code:
def output_state(state, tick)

this is where half the time is spend - at least on my system
home
puts "Simulation tick #{tick}"
filename = "tick_#{'%05d' % tick}.pgm"
File.open(filename, 'w') do |file|
file.puts <<-EOF
P2
# #{filename}
#{state.first.length} #{state.length}
2
EOF
state.each do |row|
row.each do |elem|
file.puts elem.to_s
end
end

replacing this with

file.puts(
state.map do |row|
row.join("\n")
end.join("\n")
)

dramatically cuts down the time spend in IO.
A simple

file.puts state

does not. Which might give a hint on why this is so slow,
puts is recursively called for every element in the array
while join is only called once for each row.
end
end

#####################################################################
# see if state is frozen out (i.e. no more vapor is present)
#####################################################################

class Array
def frozen_out?
not self.flatten.member?(1)
end
end

You create large new arrays here every tick.
To be fair replace it with

def frozen_out?
not any? {|row| row.member?(1)}
end
#####################################################################
# the simulation itself
#####################################################################

settings = get_settings
cols = settings["cols"],
rows = settings["rows"],
prob = settings["prob"]
state = initial_state(cols, rows, prob)
tick = 0
cls
while true
output_state(state, tick)
break if state.frozen_out?
tick += 1
offset = (tick + 1) % 2
i = offset
while i < rows
i1 = (i + 1) % rows
j = offset
while j < cols
j1 = (j + 1) % cols
if [ state[j],
state[j1],
state[i1][j],
state[i1][j1] ].member?(2)


this creates new arrays in the innermost loop....
better do a simple

if (state[j] == 2 ||
state[j1] == 2 ||
state[i1][j] == 2 ||
state[i1][j1] == 2)

if you care for speed (you do that in C)
state[j] = 2 if state[j] == 1
state[j1] = 2 if state[j1] == 1
state[i1][j] = 2 if state[i1][j] == 1
state[i1][j1] = 2 if state[i1][j1] == 1
else
if rand < 0.5
state[j], state[j1], state[i1][j], state[i1][j1] =
state[j1], state[i1][j1], state[j], state[i1][j]
else
state[j], state[j1], state[i1][j], state[i1][j1] =
state[i1][j], state[j], state[i1][j1], state[j1]
end
end


parallel assignments do create arrays also.
do the same as in C:

else
h00 = state[j];
h01 = state[j1];
h10 = state[i1][j];
h11 = state[i1][j1];
if (rand < 0.5)
state[j] = h01;
state[j1] = h11;
state[i1][j] = h00;
state[i1][j1] = h10;
else
state[j] = h10;
state[j1] = h00;
state[i1][j] = h11;
state[i1][j1] = h01;
end
end

j += 2
end
i += 2
end
end

[...]

Well that's it. At least my ruby version doubled its speed.
(but i only tested for small simulations, would you run these
modifications to see the difference on your system with your
data?)
Josef 'Jupp' Schugt

cheers

Simon
 

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

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,130
Latest member
MitchellTe
Top