Major refactoring

Over the last decade I've done a lot of work on the python wrappers. The wrapper-generator code in "vtkPython.c" grew from 760 lines of code mid-way through my PhD, to 5300 lines of code last month. And a lot of that code was within a single, massive function. That was the function that generated a python "wrapper" function for each method in the C++ class being wrapped. Now, my guideline is that a comfortable file length is 1000 lines, and anything over 1500 is too long.

So I split off three files: one for Python-to-C++ data conversion functions, one for reusable code-generation functions, and one for reusable text/help processing. And the main file, still 3700 lines long, was reorganized into many smallish subroutines with clear inputs and outputs. But the most important thing is that the generated code, i.e. the C++ code that this code writes out, is much cleaner and more compact than before. No goto statements or complex error handling logic, just a simple flow from beginning to end. And it is 25% faster than before.

This was the last time that I'll have to hold the whole file in my head at once, so to speak. Now it is possible to understand the program one subroutine at a time, and I won't have to do a major study of the code every time that I want to add a couple new features. Which is good, because I have many features left to add, but I'll have to do them in bits and snatches when I find the time.

More VTK in Python

Last week I switched vtkWrapPython.c over to the new vtkParse API, which means that vtkWrapPython now has access to much more information about the VTK classes than it used to. After this was done, it was amazing how straightforward it was to add new features to the python wrappers:

  • Multidimensional args like Multiply3x3(double A[3][3], double x[3], double y[3]) are wrapped.
  • Reference args like GetDistance(double p1[3], double p2[3], double &d) are wrapped.
  • The types size_t and ssize_t are wrapped.
  • All typedef'd types are wrapped, as long as they resolve to a supported type.
  • Methods with optional args with default values are properly wrapped.

All of these changes except for reference args were only possible because of the move to the new vtkParse API. The old API simply did not make enough information available.

The reference arg support required a different bit of trickery. Python's ints and floats are immutable (as are Python strings) so it is impossible to change their value through a reference arg the way that C does. So the obvious solution was to make a new python type that is a mutable numeric type. My new mutable() type behaves just like a number, except that its value can be changed. You construct a mutable object like this, mutable(2), and it will behave just like a "2". I also plan to make it so that mutable('string') will generate a mutable string object.

Pointers are still not wrapped, except in cases where the wrappers know the size of the memory area that they point to. Proper wrapping of pointers is dependent on developing a hinting system that will allow the wrappers to deduce the size. For the vtkDataArray::SetTuple(double *) method, I have hard-coded a hint that the size is given by GetNumberOfComponents(). This works fine for this one class, but there are tons of pointer methods in VTK and they cannot all be hard-coded into the wrappers. Once a good hinting scheme is in place, most of these methods can be automatically wrapped. That is a project that will have to wait for another day.

Calories on-the-go

The distance running continues. Last week I did 34km again, and this week I did 35km. These long runs are killers, but I've started packing Gatorade and the extra electrolytes are just what I needed to avoid the post-run nausea. It's a huge relief that I no longer have to get sick after every long run. I still feel like crawling into bed afterwards, but that’s to be expected after burning 2000+ calories.

Speaking of calories, when I first looked up Gatorade on the internet, caloriecount.about.com said it had 220 calories per 250mL. So if I drank 250mL for every 20 minutes of vigorous exercise, that would be 1980 calories for a 3 hour run. How could anyone take in that much sugar without getting sick? And more to the point, if someone was exercising in order to lose weight, that much sugar will make the exercise completely pointless. Fortunately, a bit more looking led me to realize that only Gatorade "Carb Energy Drink" has so many calories, and it is meant for carb loading, not for hydration. The Gatorade "Orange Drink" (which I bought) has only 64 cal per 250mL, and since I bring about 1200mL on my run, that's only about 300 cal in total. That's as many calories as a light breakfast. But even then, the stuff feels sticky in my mouth. I miss my water.

I've also been trying to improve my gait, in order to make better use of my "leg springs". In my old style of running, I would roll my foot heel-to-toe because it resulted in a silent footstrike, and I thought that silence meant that I wasn't doing any breaking when my foot struck. This kind of footstrike actually results in the foot landing too soon, and with too much extension, which might be why I had iliotibial band syndrome for a while. Now I am trying to land with a flat foot, with my knee bent, and immediately springing forward with a strong push. It feels like it takes more effort, and it is noisier than my old style (my foot slaps noisily on the ground), but hopefully it will improve my efficiency in the long run. Get it? In the "long run"? Ah, forget it, comedy was never my thing.

Trail running on Bowen Island

I participated in Run For The Ferry on Bowen Island last Saturday. This was my first trail run and my second competitive 10k run. My time was 40:32, nearly identical to my Stampede Road Race time of 40:19. The lack of improvement is kind of disappointing, but then again, the trail had plenty of hills and was more difficult than a typical road course.

This will be my last competitive run until the spring. My goal of running a 10k in under 40min can wait until then, and in the meantime I will continue with endurance training.

Python ghosts

Python and VTK each have their own garbage collection systems, with reference counts as the primary means of tracking references. The two GC systems are tied together by a very simple rule: the python wrappers hold only one reference to the VTK object. Because of this simple rule, there are only two places in the Python wrapper code where we have to worry about VTK reference counts: 1) the point where VTK objects first enter Python and have a PyVTKObject constructed for them, and 2) the point where the PyVTKObject is destroyed by Python's GC system.

The above system would work perfectly except that, several years ago, I added a Python "dict" to PyVTKObject so that python users could add custom attributes to their VTK objects. I even made it possible to add new methods by subclassing VTK classes within Python. Hence, VTK objects can by extended with pure-python attributes and methods. The problem was that these attributes would only last for as long as the PyVTKObject existed. So if a PyVTKObject was e.g. stored in a vtkCollection and then retrieved, it would come back as a stripped object with all of its pythonic attributes removed because only the vtkObject and not the PyVTKObject would be stored. I would work around this by always using pythonic containers to store python-customized VTK objects.

I have finally fixed the python wrappers so that this unexpected stripping of pythonic attributes no longer occurs. Whenever a PyVTKObject is destroyed, its attributes are saved in a 'ghost' object. The ghost is simple, it is just a C struct that holds the pythonic attributes and a weak pointer to the VTK object. Every time a new ghost object is to be added to the list, the weak pointers are checked, and any ghosts for VTK objects that no longer exist are erased. This keeps the "graveyard" from growing continuously.

So now whenever a VTK object pointer enters Python, the graveyard (the list of ghosts) is checked to see if the object used to have special pythonic attributes. If so, then a PyVTKObject is constructed with these attributes. So to all appearances, the user will be getting the old object back again, but really, they are getting a resurrected ghost. I really don't think that I did a good job of explaining all of that. But since I'm just filling some quiet time during my vacation, it will just have to stand as it is.

Cleaning up VTK’s weakrefs

Today I replaced the internals of VTK's weak pointer implementation. The weak pointers added an observer to their object so they could catch the DeleteEvent signal that every VTK object emits when it destructs. This made weak pointers very resource-intensive, and it also meant that a weak pointer could be left with an invalid pointer if someone called RemoveAllObservers() on the object. My new implementation adds an optional list of weak pointers to vtkObjectBase. This is very lightweight, and it means that observers and weak pointers will no longer get in each other's way.

Since this new implementation works with vtkObjectBase instead of vtkObject, it will also allow me to do a useful thing with the VTK Python wrappers. It used to be that when a python reference to VTK object went out of scope, the pythonic part of the VTK object would be destroyed, even if C++ references to the object still existed. So if the VTK object was later returned to Python, the pythonic part would have to be recreated and important parts of it like its python "dict" would be lost. With weak pointers, I can make an engram of the python object before it destructs, and then resurrect it when the VTK object returns to Python, or I can mark the engram for deletion when the VTK object destructs.

My own preprocessor

This was a fun little project. I wrote a C++ preprocessor for WrapVTK and will probably add it to VTK itself sometime this week. This preprocessor is specifically meant to be used with a wrapping system, it includes all header files and stores all macros but it does not expand them, instead it allows the wrappers to wrap them. Except for the lack of macro expansion, it is a full preprocessor. It can evaluate expressions and handle #if directives. It can provide an integer value for any macro that will evaluate to an integer after expansion.

The addition of a preprocessor to the VTK wrappers allows a bunch of "hocus pocus" to be removed from the wrapper code. No longer will inscrutable regular expressions be needed to block out odd code chunks that can't be wrapped. No longer will the wrapper-generators need to rely on #ifdefs to decide what types can be wrapped. Instead, the parser will be able to correctly follow the #ifs and #elses in the wrapped header files themselves. Even better, difficult header files like vtkType.h and vtkConfigure.h can be wrapped and all the constants that they define can be made available in Python. It's all good.

Some perspective

This post is to answer the question of why I run, and what I want to achieve. My running started in April 2009, and it started because I was out of shape. For five years I'd been out of shape and it was obvious that it was bringing down my mood and my energy level. Starting up again, though, is never easy, and the fact that the air here in Calgary is considerably more rarified than what I was breathing in Ontario did not help.

So, going back to 2009, I decided to start doing 4km a day of running, walking, or whatever. Hard to believe that it took a few weeks before I could easily run a full mile. During my grad studies, I used to run two miles to the university as a matter of course. Anyway, after starting small, I found that after a few months I could run a full 10k with difficulty. That is what I considered my "long run", and it was something that I would do every two or three weeks.

Times have changed. I can easily do a 10k before breakfast, and I do so three times a week. My long runs are 30k every weekend. I've been pounding 50 or 60k of asphalt every week for over three months. When I reached 50k per week, I thought that it was a nice level to stick to. But I've been pushing the mileage further and further, just because I can. And this brings us to the key question: Why? Well, obviously because I want to try a marathon, and even though my next opportunity will be in the spring, I want to know now whether I am capable. That is, I want to know if I can consistently and comfortably run over 30k, because if I can do that, then I can run a marathon with good time. And about wanting to run a marathon, yes, it does have a lot to do with the fact that I'll be turning 40 in a couple years.

So, there you have it. I run for health, but I also want to compete. So probably the Calgary Marathon in the spring, maybe Regina the next fall, or who knows, maybe Boston would be fun (ha ha). Those will require considerably more training than the 60km per week than I am testing myself with right now. After getting a couple marathons under my belt, I'll drop back to 50km per week which is a less competitive but certainly a healthier mileage. Because, you know, by then I'll be forty and old guys need to take it easy.

… And 34km

Yup, after taking a break last week I have bumped up my mileage once again, to 21 miles, or 34km, or most importantly to the point where many runners hit "the wall". I am happy to report that there was no wall. The run was no more difficult than my last few long runs. It was, however, followed by the usual bout of nausea that had me hiding under the sheets for a couple hours. It couldn't have been the heat, not on a cool day like today. Lack of salt? I'll have to eat a bag of potato chips the night before my next long run to see if that helps, or bring along gatorade instead of just water. Or maybe I just have to keep training at this level until my body gets used to it. After all, 34km is the maximum training distance that is recommended for marathon training, so when I can do this run without any problems, I'm good to go.

In any case, the nausea really isn't so bad. After the two hours are up, it's all over and I'm feeling right as rain again. Yeah, it's easy to say "that wasn't so bad" when it's all over, I know, but compared to, say, having the flu for a few days it really is nothing. Also, since my vacation starts next weekend, it will be three weeks before I have to go through it again.

Oh yes, and I finally replaced my old running shoes, but not before putting 1200km on them. Now I'm the proud owner of two new pairs of Asics GT-2140’s, last years model, very comfortable and very inexpensive, and I ignored the salesperson's warning that running shoes have a "shelf life." I'll wear them out long before they start to rot.

Now up to 30km

This morning was my first 30km practice run, after several weeks of slowly building up distance. Next week is a rest week, so next weekend will just be 24km, but the week after that I will be aiming for 32km and beyond.

About the run itself, this is one that didn't go so well. The run itself was fine, it was the aftermath that was painful; apparently my body is still adjusting to the summer heat. Within a few minutes of arriving home the nausea started, and it was combined with a burning hunger and absolute lack of energy. Unluckily, my fridge was empty except for milk and orange juice, which ended up forming a rather unpleasant curdled mess in my stomach.

I wanted to do nothing except curl up in my bed until the nausea stopped, especially since I was also experiencing the cold shivers that often follow overheating. As it was, though, I was a sweaty mess, so I pulled off my clothes and sat in the bathroom until I had recovered enough to take a shower. After that, I needed a two hour nap and an hour of light reading before I was feeling like myself again. So a two and a half hour run had cost me half a day.

The good news is, the recovery was complete, and after getting out of bed, I was only suffering from tired legs and a massive hole in my stomach. Later in the afternoon I was also suffering from a $120 grocery bill that included several items that I don't usually buy, mostly salty stuff. I wonder why?

Running Route 9