Wednesday, March 13, 2013

First MOOC

I've always prided myself on being a lifelong learner. I've been watching the rise of massive open-source on-line courses with great interest and curiosity. All of my education was done the traditional way: sit in a classroom with a lecturer and other students on a fixed schedule, do homework, take tests, let material diffuse into your brain over a week or a semester and hope it sticks.

I've never taken a class on line. How would it feel? Could it be as effective as the traditional approach?

I was excited when I first heard about AI class being offered by Peter Norvig and Sebastian Thrun. Peter Norvig is the Director of Research at Google and the author of Artificial Intelligence: A Modern Approach. He's written some terrific stuff, including Teach Yourself Programming In Ten Years and How To Write A Spelling Corrector. The latter is astounding. When I get on an airplane I get myself a drink and decide which movie I'm going to watch; Peter Norvig writes a statistics-based spelling corrector in 21 lines of Python code that's 70-80% accurate. It was a revelation to me when I first saw it.

I signed up and started with the best of intentions, but then Hurricane Irene knocked our power out for ten days and put me behind the eight ball. No Internet; no computer; no lectures.

My interest in statistics has been growing over the last few years. I've tried to better understand the Bayes approach - what it means and how it differs from the frequentist view that I've been exposed to. I've read Doing Bayesian Analysis Using R and BUGS by John K. Kruschke. Don't let the adorable puppies on the jacket fool you: this is a terrific, well-written book. I've got blog posts describing other books about Bayes that have caught my attention.

But I've still never taken a basic statistics course. I saw that Sebastian Thrun, one of the AI class instructors, was offering intro statistics at Udacity. I liked the lectures I saw him give for the AI class, so I thought I'd give it a go. I started just after Thanksgiving, with the goal of finishing before the end of the year.

The key for me is to make regular, concentrated effort, track my progress, and make sure that I avoid long gaps between sessions. I set up an Excel spreadsheet to record the date and units I covered. It was the same approach that got me through my first half marathon: plan the work, work the plan. It made it easy to see when I had a few days without getting another dose of learning.

I didn't meet my time goal of finishing before the end of 2012, but I didn't miss it by much. More importantly, I got through the entire course - every lecture, every assignment. The programming assignments were in Python, which I loved. I have the latest version of PyCharm - the Python IDE from JetBrains, makers of the best programming tools on the planet. I have NumPy and SciPy, two terrific libraries for scientific computing and numerical methods. It made programming a pleasure.

Most importantly, I proved to myself that I can take good advantage of all the courses on-line: MIT, Stanford, Coursera, Udacity, Apple U and others.

I would still like to revisit AI class. There's a course from Stanford called Probabilistic Graphical Models that presents Markov models in depth. Linear algebra from Gil Strang at MIT would be a treat and a privilege.

But my next choice is Computing for Data Analysis by Roger Peng. Coursera isn't offering it now, but it's available on YouTube from Simply Statistics.

All kinds of knowledge is available to anyone with a computer, an Internet connection, and the drive to take it in. What a time to be alive.

Wednesday, March 6, 2013

Indexing My Diary

We had a winter storm for the ages here last month. January had been relatively mild, with little snow and below-average degree day totals. I ran outside on the road every weekend. My fitness was good; I felt strong.

This storm dropped three feet of snow in my yard. I went out on Friday night and blew 4" of snow off the driveway at 9 pm. When I went out the next morning the snow spilled over the top of my Honda snow thrower. It measures about 2' from the ground to the gas cap. When I went out the third time it spilled over the top again! The news said the storm cut a swath up the Connecticut River and left 30-36" of snow in its wake. I hit the snow jackpot.

I know there's no link between being cold and falling ill, but I was wet and chilled to the bone after each pass with the snow thrower. I felt fine that Friday when the snow started. By Sunday night my sinuses were full. On Monday it descended into my lungs. The coughing wouldn't stop. Rather than feel miserable and infect my co-workers, I decided to stay close to home. I had my work laptop with me, so I could have said I was "working from home." But I didn't want to feel guilty if the need to lie down and take a nap came over me, so I called in sick for a few days to beat it once and for all.

The funny thing is that I didn't take that nap. I've got a backlog of projects that I'm interested in finishing. I'm a little embarrassed about how long some of them have remained on the list, without any progress being made. One of them involved the electronic journal that I've kept for the last 19 years and counting. There's a folder for every year, a Word doc for every month, and an entry of one or more pages for every day that I decided to blather on about myself. It predates the coming of the World Wide Web; I started doing it on the first PC that I ever bought.

So I've got lots of stuff locked up inside. I found myself wondering "When did such and such happen? When did I last mention so and so?", but I didn't have any way to search. Then came Lucene, the Java based search engine from Apache. I downloaded the latest version and set about creating an index for my journal. Reading and parsing the Word documents was difficult. I used the Apache POI library, because I started with a Word 97 template; docx didn't come along until much later. I didn't like the API or documentation much, but Google found a terrific link that got me off the dime.

I fell into a nice rhythm: code, test, check in, rinse, repeat. I use Git as my local repository and a GitHub account as my master. There were problems and obstacles to overcome, but I persisted and found my way through all of them. It was a satisfying feeling when I created an index and searched it for a few terms that I knew the answer to. When I typed in "Celine", my youngest sister's name, the first entry that came back was a one-sentence entry that must have been rushed. It puzzled me at first, but I think the frequency of her name was high because the entry was so short. Fortunately her wedding day was high on the list, too. She's mentioned often on that day, but it's a longer entry so the word frequency of her name is smaller. I'll have to dig into the internals to see if I can better understand and optimize my searches.

I checked the code into GitHub; there's read-only access granted to the curious at git://github.com/duffymo/diary-index.git.

I plan to put Apache Solr on top of my index so I can have a lovely web interface. I'd also like to leverage either a timed service or a Java 7 file watcher to update my index on a schedule or whenever I make a new entry. I'm also considering abandoning Word and keeping my diary in TeX. Keeping all my thoughts in plain text will insulate me from the whims of format changes in Word...and I love TeX. (I typeset my dissertation myself using LaTeX.) PDFs can be beautiful.

I felt healthy again by the time I went back to work. It was also a reminder of how much fun it is to fall into a long, sustained coding trance and produce something that's useful and beautiful at the end.

I'm onto the next project on my To-Do list. More to come soon.

Tuesday, January 1, 2013

Looking Forward To 2013

We're all starting another year today. I haven't written in a while, in spite of all the activity that's been going on. Like most people, I usually find myself in a Janus mood at this time of year: looking back, looking ahead.

I'd say that 2012 was a good year for me. I saw both of my beloved daughters earn Master of Science degrees and start on their careers. Both are handling their business and pushing forward on their own. What more could any parent ask for?

I had the chance to travel with my youngest brother this summer. We attended a fantastic family wedding and got to spend some great time together. I enjoyed every moment of it, never to be forgotten.

I wanted to learn how to run again in 2011. By June of 2012 I had been on the road for a whole year without significant injury. So I set a new goal: train for and finish a half marathon. I fulfilled it in fine style, finishing in less than two hours. It was such a satisfying, enjoyable experience. I've continued to run regularly since. I tripled the mileage that I logged in 2010. I'd like to up the ante in 2013 and finish my first marathon. I don't know if it'll be the Hartford marathon or something earlier, but I know I'll be able to do it. Past history is a fine predictor.

It wasn't my best swimming year. I was on track for something special at the end of June. I decided to try an experiment: Was swimming in chlorinated water having an adverse effect on my lungs? I took two full months off, not swimming a yard, running full time. I'm happy to report that my lungs are better. I've been back in the water in December. The pool that I use has dropped their water temperature down to 80 F and gotten the chlorine levels under control. I've enjoyed being back in the water again. I plan to mix running and swimming all year in 2013.

Now that I can manage all three events - run, bike, and swim - I'd love to enter one of the sprint triathlons they have over the summer in Lake Terramuggus in Marlborough CT and see how I fare.

I had a great year reading, both technical stuff and fiction. It's one of life's greatest pleasures for me. I've described some of the books in this blog. Two that I didn't write about were The Swerve and Republic, Lost. Both describe how dogma holds back society and progress. Let's hope we can make headway against it in 2013.

I've always prided myself on being a lifelong learner, but I realized this year that I needed to go back to the beginning and learn something difficult. I would like to become far more proficient with machine learning software like Mahout and Weka.

I would like to upgrade my understanding of fundamentals in statistics. I finally understand the Bayes point of view after reading The Theory That Wouldn't Die, Doing Bayesian Analysis, and The Signal and the Noise. I want to deepen my knowledge and apply it in significant ways in 2013.

I'm taking my first on-line course at Udacity. I'm on track to finish it sometime in the next two weeks. Then I'll take another, and another. I'd like to widen the set of topics I'm knowledgeable about in 2013.

I signed up for an Amazon Web Services account. I'm still amazed at how much it offers at such low cost. I plan to learn it deeply in 2013.

I signed up for a Twitter account during the autumn. Aside from the time wasting and humor, it's been educational to see the the different kinds of people out there. It's been good to be exposed to different points of view.

It's exciting to start another year. I can't wait to see how this one goes.

Saturday, October 20, 2012

Mythbuster

I'm asked to use an automated code review tool at work. It's not FindBugs; nevermind the name. Given a repository URL, it checks for architectural integrity, programming practices, and documentation. It reports scores on the academic scale, with zero being the worst and 4.0 being the best possible score. It flags offensive components and presents citations to buttress its arguments.

I'm particularly bothered by one offense against architectural integrity. It says that instantiation of objects inside a loop is inefficient and gives the lowest possible architectural score. The goal is to avoid instantiation of too many objects.

How on earth is a developer supposed to populate a list of objects?

Besides, this might have been a serious issue on the earliest versions of the JVM. But object creation now rivals C in speed, and newer generational garbage collectors are also big improvements.

Rather than rail against the injustice, I decided to create a small benchmark and see for myself.

I started with a simple Person class:

package cast;

import org.apache.commons.lang3.StringUtils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
* Person
* @author mduffy
* @since 9/19/12 7:38 AM
*/
public class Person {
private static final DateFormat BIRTH_DATE_FORMATTER;

private final String firstName;
private final String lastName;
private final Date birthDate;

static {
BIRTH_DATE_FORMATTER = new SimpleDateFormat("yyyy-MMM-dd");
BIRTH_DATE_FORMATTER.setLenient(false);
}

public Person(String firstName, String lastName, Date birthDate) {
this.firstName = (StringUtils.isBlank(firstName) ? "" : firstName);
this.lastName = (StringUtils.isBlank(lastName) ? "" : lastName);
this.birthDate = ((birthDate == null) ? new Date() : new Date(birthDate.getTime()));
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

public Date getBirthDate() {
return new Date(birthDate.getTime());
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Person");
sb.append("{firstName='").append(firstName).append('\'');
sb.append(", lastName='").append(lastName).append('\'');
sb.append(", birthDate=").append(BIRTH_DATE_FORMATTER.format(this.birthDate));
sb.append('}');
return sb.toString();
}
}


I needed to keep statistics on wall time and heap used, so I wrote a Statistics class:

package cast;

import org.apache.commons.lang3.StringUtils;

import java.util.Collection;

/**
* Statistics accumulates simple statistics for a given quantity "on the fly" - no array needed.
* Resets back to zero when adding a value will overflow the sum of squares.
* @author mduffy
* @since 9/19/12 8:16 AM
*/
public class Statistics {
private String quantityName;
private int numValues;
private double x;
private double xsq;
private double xmin;
private double xmax;

/**
* Constructor
*/
public Statistics() {
this(null);
}

/**
* Constructor
* @param quantityName to describe the quantity (e.g. "heap size")
*/
public Statistics(String quantityName) {
this.quantityName = (StringUtils.isBlank(quantityName) ? "x" : quantityName);
this.reset();
}

/**
* Reset the object in the event of overflow by the sum of squares
*/
public synchronized void reset() {
this.numValues = 0;
this.x = 0.0;
this.xsq = 0.0;
this.xmin = Double.MAX_VALUE;
this.xmax = -Double.MAX_VALUE;
}

/**
* Add a List of values
* @param values to add to the statistics
*/
public synchronized void addAll(Collection values) {
for (Double value : values) {
}
}

/**
* Add an array of values
* @param values to add to the statistics
*/
public synchronized void allAll(double [] values) {
for (double value : values) {
}
}

/**
* Add a value to current statistics
* @param value to add for this quantity
*/
public synchronized void add(double value) {
double vsq = value*value;
++this.numValues;
this.x += value;
this.xsq += vsq; // TODO: how to detect overflow in Java?
if (value < this.xmin) {
this.xmin = value;
}
if (value > this.xmax) {
this.xmax = value;
}
}

/**
* Get the current value of the mean or average
* @return mean or average if one or more values have been added or zero for no values added
*/
public synchronized double getMean() {
double mean = 0.0;
if (this.numValues > 0) {
mean = this.x/this.numValues;
}
return mean;
}

/**
* Get the current min value
* @return current min value or Double.MAX_VALUE if no values added
*/
public synchronized double getMin() {
return this.xmin;
}

/**
* Get the current max value
* @return current max value or Double.MIN_VALUE if no values added
*/
public synchronized double getMax() {
return this.xmax;
}

/**
* Get the current standard deviation
* @return standard deviation for (N-1) dof or zero if one or fewer values added
*/
public synchronized double getStdDev() {
double stdDev = 0.0;
if (this.numValues > 1) {
stdDev = Math.sqrt((this.xsq-this.x*this.x/this.numValues)/(this.numValues-1));
}
return stdDev;
}

/**
* Get the current number of values added
* @return current number of values added or zero if overflow condition is encountered
*/
public synchronized int getNumValues() {
return this.numValues;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Statistics");
sb.append("{quantityName='").append(quantityName).append('\'');
sb.append(", numValues=").append(numValues);
sb.append(", xmin=").append(xmin);
sb.append(", mean=").append(this.getMean());
sb.append(", std dev=").append(this.getStdDev());
sb.append(", xmax=").append(xmax);
sb.append('}');
return sb.toString();
}
}


Then I wrote a test method to exercise creating a million instances and compared the two idioms:

package cast;

import java.util.Date;
import java.util.List;
import java.util.Random;

/**
* InstantiationInsideLoopTest attempts to answer the question: Is it bad to instantiate an object inside a loop
* with a modern JVM?
* @author mduffy
* @since 9/19/12 7:35 AM
*/
public class InstantiationInsideLoopTest {
private static final int DEFAULT_INSTANCES = 1000000;
private static final int DEFAULT_REPETITIONS = 5;
private static final String[] FIRST_NAMES = {
"Abel", "Bob", "Charlie", "David", "Edward", "Fred",
};
private static final String[] LAST_NAMES = {
"Smith", "Jones", "Carver", "Brown",
};

private Random random;
private Statistics begHeapStats     = new Statistics("beg heap (MB)");
private Statistics endHeapStats     = new Statistics("end heap (MB)");
private Statistics wallTimeStats    = new Statistics("wall time (sec)");

public static void main(String[] args) {
int numRepetitions = ((args.length > 0) ? Integer.valueOf(args[0]) : DEFAULT_REPETITIONS);
int numInstances = ((args.length > 1) ? Integer.valueOf(args[1]) : DEFAULT_INSTANCES);
boolean isInside = (args.length > 2) && "inside".equalsIgnoreCase(args[2]);
System.out.println(String.format("# reps: %d # instances: %d inside? %b", numRepetitions, numInstances, isInside));
InstantiationInsideLoopTest tester = new InstantiationInsideLoopTest();
if (isInside) {
tester.instantiateInside(numInstances, numRepetitions);
} else {
tester.instantiateOutside(numInstances, numRepetitions);
}
}

public InstantiationInsideLoopTest() {
this(System.currentTimeMillis());
}

public InstantiationInsideLoopTest(long seed) {
this.random = new Random(seed);
}

private void instantiateOutside(int numInstances, int numRepetitions) {
this.begHeapStats = new Statistics("beg heap (MB)");
this.endHeapStats = new Statistics("end heap (MB)");
this.wallTimeStats = new Statistics("wall time (sec)");
for (int i = 0; i < numRepetitions; ++i) {
long begTime = System.currentTimeMillis();
long begHeap = Runtime.getRuntime().freeMemory();
Person p;
for (int j = 0; j < numInstances; ++j) {
p = new Person(getFirstName(), getLastName(), getBirthDate());
}
long endHeap = Runtime.getRuntime().freeMemory();
long endTime = System.currentTimeMillis();
//          summarize("outside", i, begTime, endTime, maxHeap, begHeap, endHeap, persons);
}
System.out.println(this.begHeapStats);
System.out.println(this.endHeapStats);
System.out.println(this.wallTimeStats);
}

private void instantiateInside(int numInstances, int numRepetitions) {
this.begHeapStats = new Statistics("beg heap (MB)");
this.endHeapStats = new Statistics("end heap (MB)");
this.wallTimeStats = new Statistics("wall time (sec)");
for (int i = 0; i < numRepetitions; ++i) {
long begTime = System.currentTimeMillis();
long begHeap = Runtime.getRuntime().freeMemory();
for (int j = 0; j < numInstances; ++j) {
Person p = new Person(getFirstName(), getLastName(), getBirthDate());
}
long endHeap = Runtime.getRuntime().freeMemory();
long endTime = System.currentTimeMillis();
//            summarize("inside", i, begTime, endTime, maxHeap, begHeap, endHeap, persons);
}
System.out.println(this.begHeapStats);
System.out.println(this.endHeapStats);
System.out.println(this.wallTimeStats);
}

private String getFirstName() {
return FIRST_NAMES[this.random.nextInt(FIRST_NAMES.length)];
}

private String getLastName() {
return LAST_NAMES[this.random.nextInt(LAST_NAMES.length)];
}

private Date getBirthDate() {
return new Date(this.random.nextLong());
}

private void summarize(String testName, int repetition, long begTime, long endTime, long maxHeap, long begHeap, long endHeap, List objects) {
System.out.println(String.format("test name    : %s", testName));
System.out.println(String.format("repetition # : %d", repetition));
System.out.println(String.format("list size    : %d", objects.size()));
System.out.println(String.format("beg heap (MB): %d", begHeap / 1024L / 1024L));
System.out.println(String.format("end heap (MB): %d", endHeap / 1024L / 1024L));
System.out.println(String.format("max heap (MB): %d", maxHeap / 1024L / 1024L));
System.out.println(String.format("wall time (s): %10.3f", (endTime - begTime) / 1000.0));
for (int i = 0; i < 5; ++i) {
System.out.println(objects.get(i));
}
}
}

I ran this test case on two different machines - one running Windows XP, another using Windows 7 - and both running Sun JVMs for Java 6. The results consistently said that the idiom recommended by the automated code review suite was incorrect. I understand the dangers of benchmarks like these, but it's comforting to attempt to be scientific rather than merely complain.

Sunday, October 14, 2012

Race Day

I was nervous during the week leading up to the race. My last long run went very well. I did another six mile run the following Saturday, followed by an easy three mile jaunt the next day that included my first taste of gel packs, those doses of instant fuel that could keep me from hitting the wall at the end of the race. I followed the "don't introduce anything new on the day of the race" mantra and tried one. The vanilla flavor was delicious; it went down easily but took time to get out of the package; most importantly, it didn't upset my stomach.

It was a week to taper, according to my plan, but I ended up doing nothing. Yoga was cancelled due to Columbus Day. I didn't run at all, because my wife was in the midst of a cold for the entire week, as were my co-workers. Disease was all around me! I was concerned about waking up on race day with a whopper of a cold that would put me on the sidelines. So I went to bed early every night and hoped that my immune system would fight the good fight.

Race Day dawned bright and cold. Temperatures were below freezing overnight. What to wear? I decided to run in shorts and forego the tights. I had an Under Armor shirt with the half-marathon long-sleeved t-shirt over the top. I wore the same baseball cap that I'd done all my training in; no hat over my ears. I heeded the "no changes on race day" advice and ran in my minimals. I was determined to not give in to sneaker fear. My one concession to the cold was gloves. That turned out to be a very smart thought.

We drove into the city, parked in the convention center garage, and walked over to Bushnell Park. We had time, but there wouldn't be too much standing around. I was in line, right next to the "9 minute mile" sign, with only twenty minutes til race time. The crowd was shoulder to shoulder. Would the start be as viscous as the Manchester Road Race? It only took me two minutes to cross the starting line, and I was running comfortably right away. I hit the start button on my stopwatch and fell into my cadence - ninety right leg strikes per minute. The full and half marathon groups ran in one pack at the beginning. My biggest concern was taking a wrong turn and getting lost. But that turned out fine. There was a big sign at Main Street telling full marathons to turn left and half marathoners to turn right. After that, all I had to do was follow the crowd.

I didn't feel warm for the first mile or two, but everything was calm and relaxed. My breathing was smooth and comfortable. Nothing hurt. There was a Gatorade stop around mile two. I decided to take a cup at each stop to make sure that I didn't dehydrate. I didn't hurry. I'd stop, take the cup, gulp it down, and then head off. Drinking and running don't mix any more than drinking and driving do. I made sure that all my paper cups made it into a bin. The street was covered with crushed cups and spilled Gatorade. The volunteers would have a big cleanup job.

The training plan works. There were no official mile markers, but I got the idea that I was maintaining a nine-minute per mile pace without any strain. When I ran under the halfway arch the clock read 59 minutes. It was the first time I thought that I could actually break two hours. I noticed a woman wearing a yellow shirt that said "2:00 finish" on the back. She was the pace keeper: as long as I could stay with her I'd have a chance of meeting my time goal.

I was ahead of her when I entered Elizabeth Park, but some gentle slopes slowed me down more than I realized. I could see the Travelers tower ahead when I ran down Albany Ave past the eleven mile marker. The pace lady was within sight but ahead of me, surrounded by suffering acolytes who hoped for the reward of a two hour finish time. I had to decide: settle into a relaxed pace, call it a nice effort, and miss out on a two hour finish, or pick up my cadence and catch her. It was uncomfortable, but I decided on the latter course. I heard her say "You wanna bring it home?" as I passed her. I kept going until I couldn't hear her voice anymore. Less than two miles to go? That was all I could manage when I started this journey 16 months ago, but now it was easy. I pumped my arms and legs the whole way, under the Bushnell arch, into the chute, and under the finish arch. The clock read 1:59. My watch said 1:57. My average speed was a shade under nine minutes per mile, which was better than any training run I'd done.

I surprised my wife. I walked along like a veal calf to pick up a medal, a water bottle, and a bag with a banana and some other snacks. She sounded surprised when I called her on my cell phone. "Are you done?" she asked incredulously.

I enjoyed both the journey and the destination. It's so satisfying to say that I managed such a thing at my age. I thoroughly enjoyed the experience, so much so that I'm happily thinking about another run in November.

Most importantly, I've made myself into a runner. What other transformation can I undergo? How else might I remake myself again?

Wednesday, October 3, 2012

Last Saturday I had my last long run before the half marathon on 13-Oct. It was overcast and threatening rain when I hit the Airline Trail at noon, so I had it mostly to myself. There were a few hardy mountain bikers and a dog walker or two, but the traffic was greatly diminished.

The plan called for 12 miles, but I was ahead of schedule for the last two long runs. I wanted to have the confidence of knowing that I could complete the distance. I had a water bottle filled with ice-cold green Gatorade in hand. I planned to stop every 3-4 miles for 30 seconds to get a drink and stay relaxed. I wanted to simulate my plan for the race.

The first mile was a warm-up. I had a sweatshirt on that may have been overkill. By the time I stopped for my first drink I was loose and lathered up. The markers for the fourth mile went by quickly. I was at nine minute miles or better the whole way. I never saw the fifth or sixth mile markers, same as the week before. The trail had a gentle upward slope. I felt anxious as I ran: would there be a marker for the turnaround point at mile 3? I crossed a road that intersected the trail, ran to the next gate - and there it was! I was surprised when I checked my watch: I was still maintaining a nine-minute mile pace! I felt good. I had a long, relaxed drink and started down the slope towards my car.

I have to compliment the folks who put together the plan that I've used to prepare for the race. They know what they're doing. I felt strong on the way back. When I passed the ten mile mark, I had plenty left. I thought back to how I felt at the end of that personal best long run two weeks ago. I was greatly improved. I was slowing down and suffering a bit more when I passed the twelve mile mark. I thought back to how knackered I felt at the end of last Saturday's run. I was beyond that. The last two miles were difficult. But I felt like a running man when I finished in 2:15.

Now I'm tapering down for the next two weeks. The longest run will be five miles. I'll do some swimming, shorter runs, and yoga. I'm looking forward to that day. I just hope I can dodge catching a cold for the next two weeks.

Monday, September 24, 2012

Uncharted Trails

Saturday morning meant another long run. My training plan called for ten miles, but I'd already accomplished that. It was time to go above and beyond. I was going back to the Airline Trail for a twelve mile run, longer than anything I've attempted.

Thankfully it was another perfect day. I brought a water bottle filled with Gatorade to carry with me. I resolved to stop for a swig every three miles. I found the starting mile mark at precisely noon and hit the trail.

The first miles were easy. I had no trouble knocking off the first three at a nine minute mile pace. I stopped for my first drink and felt fine.

The next three miles were on a part of the trail that was a little rougher: washed out and slightly uphill. I missed the five mile marker the week before. I had a pretty good idea of where it was this time, so I hit my stop watch as I passed it. It's a good thing I did, because thirteen minutes later I hadn't encountered the six mile marker. I stopped for a drink, turned around, and headed back where I came from.

I was experiencing some discomfort on the way back last week. My feet were sore and tired, and my right inner thigh hurt every time I picked up my foot. Not so this week. I felt strong and determined. My pace was slower, but I was still in the 10-10:30 per mile range. I still had some energy when I passed the ten mile mark. I finished twelve miles in 2 hours 5 minutes. Not bad! Best of all, I felt okay when I walked back to the car during the cool down.

My legs were tired for the rest of the day, but I was far from wrecked. When I woke up the next day I felt stiff, but not too bad. It was another gorgeous day. I could have followed my plan and done 30 minutes of easy running. I had gas in my tank, but I decided to spend it cutting my grass instead.

Next Saturday is the last long run before the race. The plan calls for twelve; I'll go above and beyond again and shoot for 14 miles. If I can do that, I know I'll finish this race in fine style. After that, I start tapering down for the race.

I'm going to hold at this level for a while. I'd like to try a few more half marathons and see how I feel before deciding on my next step. This has been a terrific ten weeks. I doubted whether I'd be able to do this when I started, but those doubts are almost gone.