Archive for the ‘Python’ Category

Simple 2D Ray Tracer   Leave a comment

Of late I’ve been studying light, and thought it would be fun to write a program to model light striking a planar mirror. I have now got a working version out which happily traces the rays using some simple vector math and a slightly buggy ray tracing algorithm. Pictures are necessary :D

pyafg – new features!   Leave a comment

If you’ve been watching my pyafg github repo of late, you will have noticed that I’ve been very busy hacking on pyafg in the last few weeks, and have managed to add a heap of cool new features.

What’s New

  • Added a graphical transform editor
  • Added a gradient coloring system
  • Added more features!
  • Added some more fractals
  • Fixed many bugs
  • Made the pygame interface cleaner
  • You can now save images in the pygame interface
  • Added gradient demo

The transform editor was written using tkinter, and so for that reason it is neither pretty nor quick (for some reason tkinter does not support drawing directly to a canvas, you have to make objects on the canvas), but it does the job well, and I’ve managed to create quite a few neat looking fractals with it. In the future I hope to make a gtk or qt interface, but until then, it should do the job.

pyafg's tkinter transform editor

I’ll need to tidy up and standardize the command line arguments to pyafg, because I’ve added a lot more options. Here is an example of gradient coloring.

Here is a shot of the gradient for the above image.

Posted 22/05/2010 by Emmanuel Jacyna in pyafg

Tagged with , , , ,

Using python generators   Leave a comment

I just made a generator in python.

Wow. Just, Wow.

Generators are really awesome.
I’ve been having a couple issues with my IFS algorithm, because it’s not quite as fast as I would like, and I can’t seem to do everything I want just by passing callback functions. Here’s the original algorithm:

def run_IFS(iters, skip, rules, draw_method, speak_method=None, interval = 10000):
    """Calculate the points in an iterated fractal system, drawing them with draw_method,
    and announcing results with speak_method every interval iterations."""
    probs = [r[PROB_I] for r in rules]
    rules = [r[RULE_I] for r in rules]

    x = random_float()
    y = random_float()

    while i < iters + skip
        chosen = choose_rule(probs)
        x, y = calculate_transform(x, y, rules[chosen])
        if i > skip:
            draw_method(x, y, chosen)
        #Announce the results
        if not speak_method: pass
        elif i%interval == 0:
            speak_method(x, y, i)
        i += 1

This isn’t bad code, and it’s not particularly difficult to use. I know, because it was very easy for me to add a tkinter interface in less than ten minutes. However, when I wanted to write a function to calculate the best size settings for a fractal, I found it to be rather messy:


def old_calculate_best_settings(rules, scale, iters = 10000, skip = 100):
    """Return a tuple of width and height and x_off and y_off settings
((w,h),(x_off, y_off))"""
    #note, I know this is ugly, I don't know how else to do it though
    global h_x, h_y, l_x, l_y
    h_x = h_y = l_x = l_y = 0

    def draw_method(x, y, chosen):
        global h_x, h_y, l_x, l_y
        if x*scale > h_x: h_x = x*scale
        elif x*scale < l_x: l_x = x*scale
        if y*scale > h_y: h_y = y*scale
        elif y*scale < l_y: l_y = y*scale

    run_IFS(iters, skip, rules, draw_method)
    width = int(abs(l_x)+abs(h_x))
    height = int(abs(l_y)+abs(h_y))
    x_off = int(abs(l_x))
    y_off = int(abs(l_y))

    return ((width, height), (x_off, y_off))

Yes, that’s right.

    Global Variables


Because of an interesting feature in python, it’s not possible for draw_method to change the values of h_x (and friends) without resorting to globals. This is because you can access variables from outside scope, but assigning to them makes a new local variable with the same name, which is not really what we want (thanks to bob2 from #python on freenode.net for the explanation). Of course, I could have made these variables classes, and then called some __set__ method to change their values. Purge your mind friend. The very fact that we’re thinking of such roundabout, convoluted methods is a sure sign that something is not being done right.

Enter generators

Python, of course, has a nice solution to this problem. We can see that this function is basically iterating and producing points. A possible solution would be to store the points in a list, and then iterate over these to see what the low/high points are. This is halfway there, however, when we’re talking millions of points, memory quickly becomes an issue (at least on my 486 :). Instead, we can use a generator, which behaves like a list, and makes for more “pythonic” code. Here’s a simple example:


def generate_x():
    i = 0
    while i < 10:
        print i
        yield i
        i += 1

for x in generate_x():
    print x

When you run that code (or on codepad) you can see that the prints are interleaved in the generator and the for loop, demonstrating how it works.
To change a function to a generator, we just rearrange the function a bit and make it yield rather than return the points.
Here’s the run_IFS function as a generator:


def IFS_generator(iters, skip, rules):

    probs = [r[PROB_I] for r in rules]
    rules = [r[RULE_I] for r in rules]

    x = random_float()
    y = random_float()
    i = 0

    while i < iters + skip:         chosen = choose_rule(probs)         x, y, = calculate_transform(x, y, rules[chosen])         if i > skip:
            yield (x, y, chosen)
        i += 1

That’s a lot simpler, isn’t it? We can now modify the calculate_best_settings function to use the generator:


def calculate_best_settings(rules, scale, iters = 10000, skip = 100):
    """Return a tuple of width and height and x_off and y_off settings
((w,h),(x_off, y_off))"""
    h_x = l_x = h_y = l_y = 0

    for points in IFS_generator(iters, skip, rules):
        x, y, _ = points
        if (x*scale) > h_x: h_x = x*scale
        elif (x*scale) < l_x: l_x = x*scale         if (y*scale) > h_y: h_y = y*scale
        if (y*scale) < l_y: l_y = y*scale

    width = int(abs(l_x)+abs(h_x))
    height = int(abs(l_y)+abs(h_y))
    x_off = int(abs(l_x))
    y_off = int(abs(l_y))

    return ((width, height), (x_off, y_off))

See how this is now nice, pythonic code? No more messing with global variables and callbacks. Of course, there’s also a nice speed bonus in getting rid of those callbacks.

After timing both functions, I got these results:
Old:
[4.5442321300506592, 4.4281911849975586, 4.3002359867095947] min: 4.30023598671
New:
[5.3141071796417236, 4.0443418025970459, 3.9916889667510986] min: 3.99168896675

This is a pretty significant speed up for such a small change! Note that the calculate_best_settings function only runs ~10000 iterations. When you generate a fractal with 1000000 iterations this difference becomes even greater. The code gets a boost once we’re rid of draw_method and speak_method. Of course, the next step would be to implement this function as a C extension.

Further reading:
Pep 255 – Simple Generators

Posted 19/04/2010 by Emmanuel Jacyna in Code, Python

Tagged with , , ,

pyafg – transform editor   Leave a comment

I was at school on thursday, and had IT Software Development in the afternoon. Surprisingly, it turned out to be quite enjoyable. I tested out pyafg on the school computers running windows xp, and it worked quite nicely. I can now say that pyafg is cross platform! (w00t)

Since we were being “taught” how to use tkinter, I decided to hack up a little gui for pyafg using it. It’s a rather simple affair, but is both a testament to the power of tkinter, and the success of my abstraction in libifs. I plan to turn run_IFS into a generator, and generalize it even further. While I was working on the (simplistic) gui, I had the idea to make a transform editor for pyafg. I think it should be pretty easy, and for those who’ve seen transform_viewer.py in the git repo, you’ll know that I already have the means to do so :)

I’ve also updated the pyafg page into something looking a little more like a project page, and plan to add a TODO list to that. Transform Editor will be first on the list :)

Posted 17/04/2010 by Emmanuel Jacyna in pyafg

Tagged with , , , , ,

A new project   Leave a comment

I’ve started work on a new project, pyafg (pyafg is an affine fractal generator). It’s a little python program that generates affine fractals using the iterated function system algorithm. I was inspired to start it whilst reading the computational beauty of nature; I’ll write a post up about my experiences hacking it later. For now, download it, play around with it, and enjoy!

(Note, I did only hack this up in 2 days. It’s nowhere near complete, but I’ve made a very good start to it. Expect more features!)

Computer Generated Fern

A Fern I generated

Posted 08/04/2010 by Emmanuel Jacyna in Code, pyafg, Python

Tagged with , , , ,

Python Refresher   Leave a comment

As well as learning Scheme through SICP, I also use python. In fact python was the language I originally learned programming with ~3 years ago. Of late I’ve had some fun translating a few exercises in SICP into python. However, I’ve noticed that the code I write in python tends not to be as idiomatic as it used to be, and reading some of my _old_ (as in 3+ years) code, I’ve found it difficult to understand what I was doing. Now this may simply be because I had no idea what I was doing as a newbie 3 years ago, but I’ve decided to have a run through the python tutorial again, simply to refresh my memory, and introduce me to some things I didn’t know before.

Posted 02/04/2010 by Emmanuel Jacyna in Code, Python

Tagged with , ,

Telnet and python, and the legacy of DOS   Leave a comment

I’ve been mucking around a lot with SSH and SCP lately, and because of that I’ve found myself logging into the wireless router regularly to check the IP adress of my computers. My router happens to support telnet, so I began using that instead of wasting time with the web browser. Of course, this is just the sort of task that is asking to be automated. So, a quick google, and I found http://docs.python.org/library/telnetlib.html.

So I sat down, and wrote some code:
#!/usr/bin/env python
#List hosts connected to router
import os, sys
import telnetlib
CMD = "dhcpserver status"
USERNAME, PASSWORD = "admin", "******"
HOST, PORT = "192.168.1.254", 23
tn = telnetlib.Telnet(HOST, PORT)
tn.read_until("Login:")
tn.write(USERNAME + "\n")
tn.read_until("Password:")
tn.write(PASSWORD + "\n")
tn.read_until("admin>")
tn.write(CMD + "\n")
print tn.read_until("admin>")

This is pretty much just  a small modification of the example given on the telnetlib page, and for some reason, it didn’t actually work. After a couple minutes, I realised why. My router happens to use “\r\n” as it’s newline character, so a simple “\n” wasn’t actually pushing the username and password through. After adding “\r” where necessary, it worked quite nicely. Here’s the final code, and an example of it’s usage.

#!/usr/bin/env python
#Print output of command run on router
import os,sys
import telnetlib
CMD = " ".join(sys.argv[1:])
USERNAME, PASSWORD = "admin", "******"
HOST, PORT = "192.168.1.254", 23
tn = telnetlib.Telnet(HOST, PORT)
tn.read_until(":")
tn.write(USERNAME + "\r\n")
tn.read_until(":")
tn.write(PASSWORD + "\r\n")
tn.read_until(">")
tn.write(CMD + "\r\n")
print tn.read_until(">")


xavieran@tranquility:~/Code$ ./info.py dhcpserver status
DHCP Server Lease Status
Interface 'QS_PPPoE'
IP address | Client UID/hw addr | Client Host Name | Expiry
----------------+---------------------+------------------+---------------
No network topology given - unconfigured
------------------------------------------------------------------------
Interface 'iplan'
IP address | Client UID/hw addr | Client Host Name | Expiry
----------------+---------------------+------------------+---------------
192.168.1.4 | 00:1b:77:59:e6:2e | Tranquility | 10 hours
192.168.1.2 | 00:1c:df:08:16:2b | Le-Chateau | 6 hours
192.168.1.7 | 00:15:af:80:ca:8d | aiesha-laptop | Expired
192.168.1.6 | 01:00:0e:35:ab:97:61| NB0928 | Expired
192.168.1.5 | 01:00:11:25:44:97:c2| NB0928 | Expired
192.168.1.1 | 01:00:26:c6:72:dd:a0| R8WNVML | Expired
192.168.1.3 | 01:00:27:13:68:08:fc| R8WNVML | Expired

Posted 21/03/2010 by Emmanuel Jacyna in Code, Python

Tagged with , , , ,