LocationEmailXMLTagsEditHistoryDiscussion

This page documents the participation of the team FUN in the ICFP programming contest 2009. The contest ran from June 26 to June 29.

  1. How we did
  2. Members (this year)
  3. The Task
  4. The solutions
  5. Hohmann problem
    1. 1001
    2. 1002
    3. 1003
    4. 1004
  6. Meet and Greet problem
    1. 2001
    2. 2002
    3. 2004
  7. The Python virtual machine
    1. Python Benchmark
  8. The virtual machine compiler
    1. C world
    2. Python world
    3. Running the program
  9. The Dummy virtual machine
  10. Visualizer
  11. Hill climbing
  12. Resources
    1. Computing Environment
    2. Contest resources
  13. Ending notes
  14. Other Teams
    1. Writeups
    2. Code
  15. Theory

How we did

  • We scored 831.1040 points.
  • Partial standing 170 of 317.

We didn't score more points thus we think we will descend on the final scoreboard. We really enjoyed this task and we wrote some nice programs, specially the Python Virtual Machine and the compiler that turns a problem binary into a native python module that runs very fast.

We had the first solutions (problem 1) ready for the lightning round but we didn't manage to submit them on time because of an endianness-related bug in our solution generator.

It was a little frustrating to submit the code that generates the solutions for the lightning round but not the solution themselves :-) The lack of sleep the night before didn't help (We only slept 3 hours).

In spite of this we felt happy because we were working very well as a team, even when we were a distributed team.

One of the reasons for not doing better was not having a better visualizer, specially one with zoom. We should also have worked more with physics and spent more time thinking on the problems to solve.

Members (this year)

Most of the work was done by the following members:

We worked as a distributed team. Nelson and Javier met at Nelson's and did some pair programming.

Unfortunately Juan Manuel and Oscar Andrés had to retire early. We hope to have them next year in the team.

The Task

We have a Local copy of the task.

This was a very nice task. We did analytical and brute force work toward the solutions we managed to send.

The solutions

Problems required to understand the Hohmann transfer equations. According to them, an orbitating body changes its energy in order to change its orbit, hence, it is necessary to shoot the thrusters with the initial Hohmann instant acceleration, which translates the body to the target orbit through an elliptical orbit.

Once the body reaches the more distant point in the ellipse, its radius should be equal to that of the target orbit, and the thrusters must be fired again in order to accommodate to the new circular target orbit.

Most of the time our satellite got really close to the target orbit, however, because of the inherent imprecision of our discrete calculations the destination orbit was not always circular and not always equal to the target orbit.

To compensate this we tried with an on/off control. If the target orbit was too far, we performed another Hohmann transfer to it. In the end, it proved not to be necessary to compensate in order to be within 1km of the target orbit for 900s, even though we tried it first.

if self.radius_delta_old > 0 and self.radius_delta < 0 and abs(self.r1 - self.r2) > 500 :
    self.dv = self.calculate_dv (self.r1, self.r2)
    self.dv1 = self.calculate_dv1( self.r1, self.r2)
    self.vx,self.vy = self.calculate_vector ( self.r1, sx, sy, self.dv )
    print "d > 500, adjusting ; time = " + str(self.time) + " d = " + str(abs(self.r1 -self.r2))
    self.been_shooted = False
else:
    self.vx, self.vy = 0,0

Hohmann problem

1001

  • score : 67.7723971246
  • fuel : 6050.57841675
  • steps : 19779
  • solution : src/1001.bin

For this problem you can also download the video in MPEG format: 1001.mpeg.

1002

  • score : 75.1250406346
  • fuel : 4638.87985897
  • steps : 7395
  • solution : src/1002.bin

1003

  • score : 64.0130816965
  • fuel : 7108.20406745
  • steps : 13126
  • solution : src/1003.bin

1004

  • score : 67.6606784949
  • fuel : 6075.40477892
  • steps : 17540
  • solution : src/1004.bin

Meet and Greet problem

2001

  • score : 188.399889449
  • fuel : 49111.0496937
  • steps : 20021
  • solution : src/2001.bin

2002

The following binaries do not have the last EOF frame. The server accepted them. Our Python VM will decode them.

  • score : 181.838154538
  • fuel : 46576.7525214
  • steps : 43632
  • solution : src/2002.bin

For this problem you can download the video in MPEG format : 2002.mpeg.

2004

  • score : 186.288427722
  • fuel : 49049.1265121
  • steps : 33132
  • solution : src/2004.bin

The Python virtual machine

We coded the Virtual machine in Python we never really used it much in the contest because we morphed it into a compiler that generates C code that we use from Python.

Here is the source code: python_vm.py.

The number of iterations depend on the problem.

Python Benchmark

Now let's test Psyco in a 32-bit installation (Pentium® D CPU 2.66GHz). Python 2.5.4. This is just out of curiosity because we did not use this VM much in the contest.

Problem

With Psyco

Without Psyco

1001

1589 iter/sec

589 iter/sec

2001

1050 iter/sec

368 iter/sec

4002

210 iter/sec

74 iter/sec

On this machine we got a 3X speedup with Psyco.

Without Psyco in an Intel Core 2 Duo CPU (T5750) at 2.00GHz.

  • We get 1022 iter/sec executing problem 1001.
  • We get 596 iter/sec executing problem 2001.
  • We get 127 iter/sec executing problem 4002.

The virtual machine compiler

We will describe this section in detail because this was very useful for us and we want to have the information at hand for future reference. It might also be useful for others.

A python program (with the C simulation as a module) runs the problem 4 in 46.685 seconds in a Core 2 Duo (2GHz). That is 42840 iterations/second.

A pure C implementation would be much faster but python is great for testing algorithms.

To see how much Psyco would have helped here we'll run the same problem with a slower computer (Pentium D/805 2.6G) and compare the times with and with no Psyco.

Without Psyco: 1m14.324s, ie 26909.20 iter/sec. With Psyco: 0m49.144s, ie 40696.7 iter/sec.

That's a 1.51X speedup. Unfortunately the computer with Core 2 duo was running a 64-bit Linux distro and Psyco does not run there (Psyco runs inside of a QEMU/KQEMU VM with noticeable speedups).

C world

We decided to encode the doubles as integers in order to avoid losing precision. We should have stripped the trailing zeros but we decided not to do it during the contest.

static int data[32768] = {
    0, 1072693248, 0, 0, 0, 1077805056, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1083129856, 0, 0, 0, 0, 0,
    1073741824,
    /* ... a lot of numbers */
} 

void problem_initial_data(double *d)
{
    memcpy(d, data, sizeof(double) * 16384);
}

And this the C code for the problem binary (1001 in this case) as the VM would execute it.

void
problem_timestep(double data[], double input[],
                 double output[], int *status_p)
{
    int status = *status_p;


    data[1] = data[265];


    data[4] = data[248];
    data[5] = data[4] - data[3];
    status = data[5] == 0;
    data[7] = status ? data[2] : data[1];
    data[8] = data[7] - data[0];
    data[9] = data[263];
    status = data[5] == 0;
    data[11] = status ? data[0] : data[9];
    data[12] = data[11] - data[0];
    status = data[12] == 0;
    data[14] = status ? data[8] : data[7];
    data[15] = data[264];

    status = data[5] == 0;
    data[18] = status ? data[16] : data[15];

    ...

This file is linked with each solution (code and data) in order to make a shared library. Note that we make in-place copies to C arrays here in the iterate function that is called from Python.

#include <string.h>
#include <assert.h>

/* functions you should care about, these could be called from python */
void init(double *data, double *input, double *output);
void iterate(double *data, double *input, double *output, int *status, int nsteps);

/* different for each problem */
void problem_initial_data(double *d);
/* different for each problem */
void problem_timestep(double data[], double input[], double output[], int *status_p);

#define mem_size 16384

void init(double *data, double *input, double *output)
{
  assert(data);
  assert(input);
  assert(output);

  problem_initial_data(data);

  memset(input, 0, sizeof(double) * mem_size);
  memset(output, 0, sizeof(double) * mem_size);
}

void copy_data(double *orig, double *dest) {
    memcpy(dest, orig, mem_size);
}

void iterate(double *data, double *input, double *output, int *status, int nsteps)
{
  assert(input);
  assert(output);
  assert(status);
  assert(nsteps > 0);

  while (nsteps--)
    problem_timestep(data, input, output, status);

}

We compiled the shared libraries for all the problems with this shell script:

test -d lib || mkdir lib
for i in  $(seq 1 4); do 
  python vm_compiler.py ../../input/bin$i.obf > lib/prob$i.c
  gcc -fPIC -Wall -O2 lib/prob$i.c -c -o lib/prob$i.o
  gcc -fPIC -Wall -O2 problem-common.c -c -o lib/common.o
  gcc -lc -shared -Wl,-soname,libprob$i.so -fPIC -Wall -O2 lib/prob$i.o lib/common.o -o lib/libprob$i.so 
done

Python world

The first interface in the python world is the c_vm.py file, which makes the interface with the C code for the simulator (we cannot longer call it a VM when it's in C).

We use the ctypes module in order to create native © arrays from python. This saves us a lot of time.

Note that we can save the state of the simulation. We started with a stack (that's why we have push and pop methods) but then we optimized for the hill climbing and replaced with a simple copy to native arrays.

#!/usr/bin/python

# the interfaces for this program could be improved a lot.

import ctypes
from ctypes import byref, c_int

MEM_SIZE = 2**14

# Create proxy object.
# Call as data = MEM() to create a native array of size MEM_SIZE
MEM = ctypes.c_double * MEM_SIZE
SCN_CFG = 0x3E80

# Hohmann
def get_output_1(output):
    """
    score, fuel_remaining, sx, sy, orbit_radio
    """
    return output[:5]

def set_input_1(input, *vals):
    input[0x2] = vals[0]
    input[0x3] = vals[1]
    input[0x3E80] = vals[2] # Ahh, this should not be  here.

# Meet and greet
def get_output_2(output):
    """
    score, fuel_remaining, sx, sy, orbit_radio
    """
    return output[:6]

# Clear Skyes
def get_output_4(output):
    """
    score, fuel_remaining, sx, sy, sx_fuel, sy_fuel, fuel_remain
    [sx, sy, touched?] ... (for 11 satellites)
    """
    return output[:7], output[7:7 + 3 * 11]

SG = dict() # setters and getters
SG[1] = (get_output_1, set_input_1)
SG[2] = (get_output_2, set_input_1)
SG[3] = (get_output_2, set_input_1)
SG[4] = (get_output_4, set_input_1)

class C_VM:
    def __init__(self, pr_id):
        global SG
        pr_id = int(pr_id)
        self.real_data = MEM()
        self.real_input = MEM()
        self.real_output = MEM()
        self.sim_data = MEM()
        self.sim_input = MEM()
        self.sim_output = MEM()
        self.status = ctypes.c_int(0)

        # we'll go for real initially
        self.data = self.real_data
        self.input = self.real_input
        self.output = self.real_output

        self.vm = ctypes.CDLL('../vm/lib/libprob%d.so' % pr_id)
        self.get_output, self.set_input = SG[pr_id]
        self.vm.init(byref(self.data), byref(self.input), byref(self.output))
        self.input[SCN_CFG] = -1 # Do not rely on this.
        self.iteration = 0
        self.fuel = 0

        self.savestack = list()

    def push(self):
        """ Save the state of the simulation """
        self.vm.copy_data(self.real_input, self.sim_input)
        self.vm.copy_data(self.real_output, self.sim_output)
        self.vm.copy_data(self.real_data, self.sim_data)
        self.data = self.sim_data
        self.input = self.sim_input
        self.output = self.sim_output
        self.savestack.append((self.status, self.iteration, self.fuel))

    def pop(self):
        """ Restore the state of the simulation """
        (self.status, self.iteration, self.fuel) = self.savestack.pop()
        self.data = self.real_data
        self.input = self.real_input
        self.output = self.real_output


    def iterate(self, *args):
        self.iteration += 1
        self.set_input(self.input, *args)
        self.vm.iterate(byref(self.data), byref(self.input),
                        byref(self.output), byref(self.status), c_int(1))
        rv = self.get_output(self.output)
        self.fuel = rv[1]
        return rv

Running the program

This is a skeleton of an optimizer. This was coded for the 4th problem that we didn't manage to solve.

from visualizer import CSVisualizer
from constants import EARTH_RADIUS
import v_utils

# This is an skeleton (slighty edited, it might not run)
# of how we started with a solver.

def solve_clear_skyes(sim, scn, impulse_a, angle, visualize=True):

    if visualize:
        vis = CSVisualizer(scale = 22, speed = 30)
    else:
        vis = None

    result, targets = sim.iterate(0, 0, scn) # initial impulse here, do nothing
    score, fuel, sx, sy, gasx, gasy, gas_left = result

    print 'fuel0', fuel

    old_sx = sx
    old_sy = sy

    time_solved = 0

    closest = 1e1000

    while score == 0.0:
        result, targets = sim.iterate(0.0, 0.0, scn)
        score, fuel, sx, sy, gasx, gasy, gas_left = result

        closest = min(closest, v_utils.v_norm((gasx, gasy)))

        old_sx = sx
        old_sy = sy
        if visualize:
            result.append(targets)
            result.append(sim.sim_data[0x64]) # moon_x
            result.append(sim.sim_data[0x65]) # moon_y
            vis.set_data(*result) # We don't display first timestep.
            vis.draw_map()
        if sim.iteration % 100000 == 0:
          print 'iter:', sim.iteration
          print 'closest to gas:', closest / 1000.0, ' gas left', gas_left
          print 'fuel', fuel
    else:
        print "Score", score
        print "Fuel", fuel

    return score

if __name__ == '__main__':
    import sys
    from c_vm import C_VM

    sys.path.append('../vm') # duh?

    def solve(scn):
        sim = C_VM(4)
        impulse_a = float(sys.argv[2])
        angle =  float(sys.argv[3])
        solve_clear_skyes(sim, scn, impulse_a, angle, len(sys.argv) < 5)

    solve(int(sys.argv[1]))

And this is how we would run the program with no GUI.

$ PYTHONPATH=../vm python solvercs.py 4001 0 0 nogui
fuel0 10000.0
iter: 100000
closest to gas: 100.000624888  gas left 75000.0
fuel 10000.0
iter: 200000
closest to gas: 100.000624888  gas left 75000.0
fuel 10000.0
iter: 300000
closest to gas: 99.7776569966  gas left 75000.0
fuel 10000.0
...

With GUI it would show a simple representation of the problem. The colors are not the best but we did this an hour before the contest and we didn't really use it.

The Dummy virtual machine

Manuel programmed a dummy virtual machine. It allowed him to complete solutions for the first problem with brute force even when we hadn't finished the VM. When we finished the VM we modified his solution to use the C simulator and it worked. This was an eureka moment, our very first points (2AM GMT-5 / Saturday).

Visualizer

We had an (optional) visualizer. Very basic with Pygame and with configurable speed. It was very useful but not enough. It lacked of needed features, such as Zoom, pause, etc. It would be very nice to have someone on the team only doing GUIs. It would help A LOT.

There is no point in trying to write a bunch of code and optimizations if you don't get to see what your code is doing. A team that did very well (final score was over 4000) invested time building the GUI (it does not imply causality but it is good evidence).

Visualizers other teams coded:

Please add relevant links here if you know of them.

Hill climbing

We tried to minimize with hill climbing (descending in this case). For the second problem it was not really hard. Unfortunately we were very sleepy and coded a few bugs that made us waste a lot of time.

Optimizations are fun. Even when we had a nasty bug the optimizer tried to chase the target with relative success but once we were close to it was very difficult to stay in the contact zone (1KM). The bug had to do with the direction of the force we were trying to optimize.

We fixed the bug and started optimizing direction vectors for a previously optimized optimal force. This could have worked but other team members were making progress in the problem we were solving thus we decided to stop our tests and help with other tasks. This was on Sunday.

We had code to save and restore the state of the simulation in a stack. Thus we could do recursive optimizations (single agent search) but we didn't get that far. It would have been useful when you get to be very close to the target.

#!/usr/bin/python

def problem(x): # minimize
  return (x + 2) ** 2

def optimize_descending(initial_guess, delta, min_delta, problem):
    solution = (initial_guess, problem(initial_guess))

    while True:
      next_sols = []

      for guess in [solution[0] - delta, solution[0] + delta]:
         next_sols.append((guess, problem(guess)))

      best_sol = next_sols[0] # assume this is the best.

      for sol in next_sols[1:]: # find a better one.
        if sol[1] < best_sol[1]:
          best_sol = sol
      if best_sol[1] < solution[1]:
        solution = best_sol # found a better one.
      else:
        delta /= 10.0
        if delta < min_delta:
          return solution # converged with the current delta

print 'minimum of (x + 2) ^ 2 found:', optimize_descending(2, 0.9, 1e-20, problem)[0]

This is how we used the hill climbing:

def optimize(simulator):
    def problem(accel):
        simulator.push()
        r = fitness(simulator, accel) # we had the fitness coded here...
        simulator.pop()
        return r
    return optimize_descending(100, 10, 1e-10, problem)

Resources

Resources we used during the contest.

  • IRC for communication
  • Subversion Repository
  • Internal Mailing List

Computing Environment

  • Python 2.5
  • C
    • We used GCC to compile the problem specification as a Python module.
    • We used ctypes.
    • We stored the state in C vectors and used them in-place (ctypes.c_double arrays).
  • GNU/Linux
    • Debian and Ubuntu (32 and 64 bits)

Contest resources

Ending notes

  • Because of the constant 0xCAFEBABE and being Colombians we should have named our team Juan Valdez :-) We just like the name “FUN” a lot.
  • In previous years we used many different languages. Standardizing on Python helped us a lot.
  • Once you know GIT, you start to dislike Subversion :-)

Last update: 2009-07-04 (Rev 15855)

svnwiki $Rev: 15576 $