Browse Source

initial commit, largely pulled from pdorg_site

john melesky 1 year ago
parent
commit
42ffdb8a27

+ 2 - 0
.gitignore

@@ -92,3 +92,5 @@ tramp
 # cask packages
 .cask/
 
+# ---> Pelican
+/output/

+ 89 - 0
Makefile

@@ -0,0 +1,89 @@
+PY?=
+PELICAN?=pelican
+PELICANOPTS=
+
+BASEDIR=$(CURDIR)
+INPUTDIR=$(BASEDIR)/content
+OUTPUTDIR=$(BASEDIR)/output
+CONFFILE=$(BASEDIR)/pelicanconf.py
+PUBLISHCONF=$(BASEDIR)/publishconf.py
+
+SSH_HOST=phaedrusdeinus.org
+SSH_PORT=22
+SSH_USER=jmelesky
+SSH_TARGET_DIR=/home/jmelesky/site/pd.org
+
+
+DEBUG ?= 0
+ifeq ($(DEBUG), 1)
+	PELICANOPTS += -D
+endif
+
+RELATIVE ?= 0
+ifeq ($(RELATIVE), 1)
+	PELICANOPTS += --relative-urls
+endif
+
+SERVER ?= "0.0.0.0"
+
+PORT ?= 0
+ifneq ($(PORT), 0)
+	PELICANOPTS += -p $(PORT)
+endif
+
+
+help:
+	@echo 'Makefile for a pelican Web site                                           '
+	@echo '                                                                          '
+	@echo 'Usage:                                                                    '
+	@echo '   make html                           (re)generate the web site          '
+	@echo '   make clean                          remove the generated files         '
+	@echo '   make regenerate                     regenerate files upon modification '
+	@echo '   make publish                        generate using production settings '
+	@echo '   make serve [PORT=8000]              serve site at http://localhost:8000'
+	@echo '   make serve-global [SERVER=0.0.0.0]  serve (as root) to $(SERVER):80    '
+	@echo '   make devserver [PORT=8000]          serve and regenerate together      '
+	@echo '   make devserver-global               regenerate and serve on 0.0.0.0    '
+	@echo '   make ssh_upload                     upload the web site via SSH        '
+	@echo '   make sftp_upload                    upload the web site via SFTP       '
+	@echo '   make rsync_upload                   upload the web site via rsync+ssh  '
+	@echo '                                                                          '
+	@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html   '
+	@echo 'Set the RELATIVE variable to 1 to enable relative urls                    '
+	@echo '                                                                          '
+
+html:
+	"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+clean:
+	[ ! -d "$(OUTPUTDIR)" ] || rm -rf "$(OUTPUTDIR)"
+
+regenerate:
+	"$(PELICAN)" -r "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+serve:
+	"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+serve-global:
+	"$(PELICAN)" -l "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b $(SERVER)
+
+devserver:
+	"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
+
+devserver-global:
+	$(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -b 0.0.0.0
+
+publish:
+	"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS)
+
+ssh_upload: publish
+	scp -P $(SSH_PORT) -r "$(OUTPUTDIR)"/* "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)"
+
+sftp_upload: publish
+	printf 'put -r $(OUTPUTDIR)/*' | sftp $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
+
+rsync_upload: publish
+	rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --include tags --cvs-exclude --delete "$(OUTPUTDIR)"/ "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)"
+
+
+.PHONY: html help clean regenerate serve serve-global devserver publish ssh_upload rsync_upload

+ 29 - 1
README.md

@@ -1,2 +1,30 @@
-# pdorg_pelican
+# pdorg_site
+
+
+## Installation
+
+Yeah, yeah, some documentation.
+
+This is written in Pelican, with a couple plugins, and uses a slightly-hacked `pelican-sober` theme.
+
+From scratch:
+
+  - install git and python 3 if they aren't already
+  - `pip install "pelican[markdown]"`
+  - `pip install pelican-render-math`
+  - somewhere else, `git clone https://github.com/fle/pelican-sober.git`
+    - in `static/images/icons`, put an akkoma icon
+      - first time I did this, I used `curl -o akkoma-18px.png 'https://joinfediverse.wiki/thumb.php?f=Akkoma.svg&width=18'`
+    - in `static/css/main.css`, add the line `.social a[href*='tinylad.social'] + i:before {content: url('../images/icons/akkoma-18px.png'); }` in an appropriate spot
+  - put the path to that repo in `THEME` in `pelicanconf.py` in this repo
+
+## Running locally
+
+For the [preview](http://localhost:8000), `pelican -r -l`. 
+
+
+## Deploying
+
+
+
 

+ 301 - 0
content/blog/2016-08-25-different-distances.md

@@ -0,0 +1,301 @@
+---
+title: Different Measures of Distance
+tags: geometry
+description: A few different ways of calculating distance in noncontinuous 2d spaces
+---
+
+How far is it from one place to another? How does that change if
+you're on a flat plane? What if that plane has discrete distances,
+like a checkerboard, or Civ map, or pixels on a screen?
+
+If we've got two points, *how far do you have to move to get from one
+to the other?*
+
+
+### Euclidean Distance
+
+![The shortest distance between two points](/images/post_2016_08_25/euclidean.png){width=150 height=150}
+
+This is the go-to of distance calculations. If you're talking about
+continuous, uniform planar (2d) space^[It holds for 3d and
+higher-dimensional spaces, too.], [Euclidean
+distance](https://en.wikipedia.org/wiki/Euclidean_distance) is the
+shortest distance between two points.
+
+In the most general sense, the way you get this distance is by
+measuring it. If you're lucky enough to be using [Cartesian
+coordinates](https://en.wikipedia.org/wiki/Cartesian_coordinate_system),
+then you can calculate the distance based on the distance between the
+two coordinate points. For our purposes, I'm going to use $a$ and $b$,
+located at $(x_1, y_1)$ and $(x_2, y_2)$.
+
+[Pythagoras](https://en.wikipedia.org/wiki/Pythagorean_theorem)
+figured out how to calculate the length of the hypotenuse of a right
+triangle. The fun part is that, with Cartesian coordinates, *any pair
+of points* can now define a right triangle. Just take one point with
+an x coordinate from one point and a y coordinate from the other, and
+the triangle itself can be defined as $((x_!, y_1), (x_1, y_2), (x_2,
+y_2))$^[Or $((x_!, y_1), (x_2, y_1), (x_2, y_2))$ if you're feeling
+frisky].
+
+At that point, it becomes pretty easy. The length of the two sides is
+just the absolute value of the difference between their x and y
+coordinates. So, $|x_1 - x_2|$ and $|y_1 - y_2|$. Those are easy to
+plug into the Pythagorean equation to get the distance between the
+points ($d$):
+
+$$d = \sqrt{ |x_1 - x_2|^2 + |y_1 - y_2|^2 }$$
+
+In code, this might look something like:
+
+~~~ { .python }
+def eucliddistance(x1, y1, x2, y2):
+    xdist = abs(x1 - x2)
+    ydist = abs(y1 - y2)
+    return math.sqrt( (xdist ** 2) + (ydist ** 2) )
+~~~
+
+This all seems a bit unnecessary, though, right? Cartesian coordinates
+are universal. They're how we think about points. All of our screens
+are Cartesian planes, and even our 3d worlds are Cartesian. Done and
+done.
+
+Still, I think it's good to appreciate how we got here. Why? **Because
+it doesn't work for our purposes.**
+
+[![A Euclidean distance Voronoi diagram](/images/post_2016_08_25/euclid_voronoi.png "A Euclidean distance Voronoi diagram"){width=150 height=150}](/images/post_2016_08_25/euclid_voronoi.png)
+
+A "point", to Euclid, is infinitesimal -- it has no size, nor
+shape. And a Euclidean plane is continuous -- there is no "smallest
+distance" you can move.
+
+Pixels, and graph squares, on the other hand, take up space. And if
+you want to get from $(0,0)$ to $(7,14)$ on a discrete plane, there's
+no such thing as moving 15.56247..... That's the nature of a discrete
+space -- everything's an integer. And the other fun bit is that you
+can't move in arbitrary directions. On a square grid, you've only got
+8 directions to go, at most.
+
+It is also, frankly, a bit costly to calculate. Square roots are a bit
+more complicated than adding and subtracting.
+
+Still, Euclidean distance is the definition of distance. And though we
+can't use it to determine distance of movement, we can still use it
+for comparative purposes.
+
+#### Voronoi Diagrams
+
+For illustration purposes, I've decided to use [Voronoi
+diagrams](https://en.wikipedia.org/wiki/Voronoi_diagram). Voronoi
+diagrams are a way of splitting up planes around a set of sites, such
+that all points in the plane are associated with the closest site.
+
+The simplest, slowest possible way to generate such a diagram is to
+iterate over each pixel in an image, calculate the distance between
+that pixel and each site, sort the sites by distance, and assign that
+site to the pixel. So I coded that up. I've put that code [up on
+github](https://github.com/jmelesky/voronoi_example). Nothing fancy
+going on, I assure you.
+
+By looking at the Voronoi diagrams for the same set of sites, using
+different distance measures, we can get an appreciation for how those
+measures vary, and what their characteristics are.
+
+
+### Manhattan Distance
+
+[![A taxicab distance Voronoi diagram](/images/post_2016_08_25/taxi_voronoi.png "A taxicab distance Voronoi diagram"){width=150 height=150}](/images/post_2016_08_25/taxi_voronoi.png)
+
+The island of Manhattan is densly populated, and densely built. For
+our purposes, though, it's also, notably, laid out like a grid. If you
+want to get two blocks up and two blocks over, you're going to have to
+move four total block lengths to get there. And that's a pretty
+straightforward distance measure.
+
+It's also known as taxicab distance, rectilinear distance, and a bunch
+of other things. And it's the simplest calculation we're going to be
+making. Just add the lengths of each side of our triangle:
+
+$$d = |x_1 - x_2| + |y_1 - y_2|$$
+
+In code, it might look like this:
+
+~~~ { .python }
+def taxidistance(x1, y1, x2, y2):
+    return (abs(x1 - x2) + abs(y1 - y2))
+~~~
+
+If you're on a checkerboard grid, and you are limited in movement to
+only the four cardinal directions, this is how you're going to measure
+getting from place to place. If you want to get from $(0,0)$ to
+$(7,14)$ in a taxi, you're going to go 21 squares, which is a fair bit
+further than 15.56.
+
+If you take a look at the Voronoi diagram derived from taxi distance,
+you'll see that the shapes have changed from straight-edged polygons
+to odd, almost art-deco-like shapes. In particular, some of the places
+where there were very long, thin cells in the Euclidean version have
+changed to blocky, angled, and often much distorted versions.
+
+### Civ Distance
+
+[![A 'Civ' distance Voronoi diagram](/images/post_2016_08_25/civ_voronoi.png "A 'Civ' distance Voronoi diagram"){width=150 height=150}](/images/post_2016_08_25/civ_voronoi.png)
+
+If Manhattan distance is what you reckon with when you can only move
+in the four cardinal directions, what do you do when you can move in
+all 8 possible directions? Up until the most recent edition, the
+[Civilization games](https://en.wikipedia.org/wiki/Civilization_%28series%29) had
+exactly that scenario[^1]. So let's call this the Civ distance.
+
+Calculating this one is also easy. Instead of adding the sides
+together, you just take the length of the longest side.
+
+How's that work, exactly? Well, if you're going from $(0,0)$ to
+$(7,14)$, you go at an angle for 7 squares (moving twice as fast as
+taxicabs). At that point, you're at $(7,7)$, and it's a straight shot
+to $(7,14)$ only 7 squares away. Just 14 squares total.
+
+Mathematically, it looks like this:
+
+$$d = \max(|x_1 - x_2|, |y_1 - y_2|)$$
+
+And in code, something like this:
+
+~~~ {.python}
+def civdistance(x1, y1, x2, y2):
+    return (max(abs(x1 - x2), abs(y1 - y2)))
+~~~
+
+Again, very simple. And 14 is actually shorter than 15.56. By taxi, up
+and over is a distance of two, by Euclid that same distance is 1.41
+and change. But by Civ distance, that same span is only one. That, of
+course leads to some different distortions in the voronoi
+diagram. Where the taxicab Voronoi cells stretched up, down, left, and
+right, the Civ cells are stretching further in the diagonals.
+
+Overall, Civ distance is truer to Euclid than taking the taxi, but
+it's still a very different beast.
+
+### D&D Distance
+
+Perhaps you've played some Dungeons & Dragons. Perhaps you've
+specifically played 3.5ed^[Or its offshoot, Pathfinder; or, for that
+matter, any of the d20 games that also derived from that source]. That
+game was notorious for its complex, tactical, grid-based combat
+system. Each square represented 5 feet of space, and each character
+had a limited amount of movement per turn.
+
+[![A D&D distance Voronoi diagram](/images/post_2016_08_25/dandd_voronoi.png "A D&D distance Voronoi diagram"){width=150 height=150}](/images/post_2016_08_25/dandd_voronoi.png)
+
+Now, it didn't make sense to allow only movement in the four cardinal
+directions. That would, if nothing else, be incredibly frustrating for
+players trying to move their characters around the map.
+
+They also didn't want to allow willy-nilly Civ-style movement, since
+that would offer a clear advantage to anyone who wished to use the
+diagonals[^4ed].
+
+Ultimately, they decided on an iterative process which was a bit more
+complex:
+
+  - The first time (and every odd time) you move a diagonal, it costs
+    5 feet of movement/distance, just like moving in a cardinal
+    direction
+
+  - The second time (and every even time) you move a diagonal, it
+    costs 10 feet of movement/distance, like you're taking a taxicab
+
+The net result is that, if you start out moving diagonally only, it's
+5 feet, then 15, then 20, then 30, then 35, then 45, etc. Translating
+feet to grid square units, it's 1 unit, then 3, then 4, then 6, then
+7, then 9.
+
+That pattern, as it turns out, is the floor of 1.5 * the diagonal
+distance.
+
+To generalize that to more than just the purely diagonal, we figure
+out how much we can go straight, then do the rest diagonal, and add
+them together:
+
+$$d_1 = \max(|x_1 - x_2|, |y_1 - y_2|) - \min(|x_1 - x_2|, |y_1 - y_2|)$$
+$$d_2 = \lfloor 1.5 \times (\min(|x_1 - x_2|, |y_1 - y_2|))\rfloor$$
+$$d  = d_1 + d_2$$
+
+And in code, that looks like this[^odd]:
+
+~~~ {.python}
+def dandddistance(x1, y1, x2, y2):
+    mindist = min(abs(x1 - x2), abs(y1 - y2))
+    maxdist = max(abs(x1 - x2), abs(y1 - y2))
+    return ((maxdist - mindist) + (1.5 * mindist))
+~~~
+
+Looking at the Voronoi diagram, this is easily the closest match to
+Euclid. And that's not surprsing, really. When Euclid things up and
+over is 1.41 and change, taxicabs and Civ think it's 2 and 1,
+respectively, D&D thinks it's (about) 1.5, which is much, much closer
+to the continuous distance. To get from $(0,0)$ to $(7,14)$ by way of
+D&D distance is 17, which is pretty close to the 15.56 of Euclid, and
+without the distortions of Civ.
+
+Of course, it's a bit more complex to calculate than Civ or taxi, but
+it doesn't require taking any square routes, which marks it as still
+computationally simpler than Euclid. And it's grid-native. The
+designers of 3.5 did a good job finding a relatively simple
+approximation for distance.
+
+### Conclusion
+
+And here we are at the end. Hopefully getting from point $a$ to point
+$b$ wasn't too rough, and I know I learned a thing or two on the way.
+
+Things get more complicated when you start measuring things in three
+dimensions. The D&D algorithm, particularly, is unlikely to scale well
+as you end up figuring out the appropriate values for each of the 26
+different directions you can go. Maybe I'll revisit this again.
+
+And let's not forget non-spatial distances! [Hamming
+distance](https://en.wikipedia.org/wiki/Hamming_distance) is
+particularly fun to think about. It has uses in small-scale AI tasks
+like autocorrect, as well as in very-large-dimension binary vector
+spaces[^duh]. I guess I'm saying that distances are fascinating
+things, and there's so much more to talk about.
+
+Thanks for bearing with me.
+
+
+[^1]: For an even longer time, the king piece on a chessboard had the
+exact same abilities, which is why this is probably more commonly
+known as "chessboard distance", or [Chebyshev
+distance](https://en.wikipedia.org/wiki/Chebyshev_distance), after
+Pafnuty Chebyshev, a 19th-century Russian mathematician. But I've
+played more Civ than chess, frankly.
+
+[^4ed]: When it came time to publish the 4th edition of D&D, however,
+they changed their mind on this. The complexity of the 3.5 distance
+calculation wasn't worth the verisimilitude, so they moved to using
+Civ distances instead. This decision also led to the infamous [square
+fireballs](http://diceofdoom.com/blog/2009/10/powergaming-understanding-area-of-effect-in-dnd4e/),
+but no system is perfect.
+
+[^odd]: You might notice that there isn't a `min` or `math.floor` call
+in that code. There was, for a bit, but it was generating [some
+strange artifacts](/images/post_2016_08_25/odd_dandd_voronoi.png),
+where strange striping was occurring. I suspect that's due to an odd
+quantizing effect, but haven't looked into it deeply. In the meantime,
+using the un-floored numbers produce a more coherent image.
+
+[^duh]:
+    *Obviously*
+    
+    ...
+    
+    For some machine learning tasks, you end up representing different
+    things in very large vector spaces (one dimension for each
+    "feature" you are keeping track of). In many cases, *binary*
+    features are more tractable and just as useful -- for example, you
+    might keep track of *whether* a certain word shows up in a
+    document, rather than *how many times* it shows up. When those two
+    situations coincide, Hamming distance becomes more palatable and
+    tractable than Euclidean distance.]
+

+ 115 - 0
content/blog/2016-08-29-distances-redux.md

@@ -0,0 +1,115 @@
+---
+title: Distance Measures Redux -- Unifying and Tweaking
+tags: geometry
+description: Revisiting discrete distance measures and tweaking them
+---
+
+After [talking about distance
+calculations](/posts/2016-08-25-different-distances.html) the other
+day, I was thinking about how we could generalize the measures.
+
+Looking back on the D&D distance calculation, I think it can encompass
+civ and Manhattan distances, too.
+
+![The shortest taxi drive](/images/post_2016_08_29/taxi_path.png){width=150 height=150}
+
+First, for simplicity's sake, I'm going to refer to $x_d$ instead of
+$|x_1 - x_2|$, and likewise for $y_d$. With that convenience, I'll
+contract it to a single line rather than spreading it over
+three. Finally, I'm going to discard the floor conversion ($\lfloor
+\rfloor$) for the moment.
+
+$$d = \max(x_d, y_d) - \min(x_d, y_d) + 1.5 \times (\min(x_d, y_d))$$
+
+Note that "1.5". I think that's the trick. If we manipulate that, we
+should be able to replicate the other, simpler calculations.
+
+Remember that the 1.5 represents the cost of making a diagonal rather
+than a cardinal move. Since taxis can only move cardinally, they take
+two steps to move diagonally. Let's plug "2" into that and see what
+happens:
+
+$$d = \max(x_d, y_d) - \min(x_d, y_d) + 2 \times (\min(x_d, y_d))$$
+
+The $\min$ cancels out a bit:
+
+$$d = \max(x_d, y_d) + 1 \times (\min(x_d, y_d))$$
+
+Which further simplifies:
+
+$$d = x_d + y_d$$
+
+Which is the Manhattan formula. Success!
+
+
+![The shortest discrete drive](/images/post_2016_08_29/other_path.png){width=150 height=150}
+
+Let's try it for the Civ distance. Where taxis take 2 moves to go
+diagonally, civilizations do it in 1:
+
+$$d = \max(x_d, y_d) - \min(x_d, y_d) + 1 \times (\min(x_d, y_d))$$
+
+Those $\min$'s cancel out completely:
+
+$$d = \max(x_d, y_d)
+
+... which is, again, exactly how we calculate Civ distance. Excellent,
+well done!
+
+
+That leaves us with the general formula:
+
+$$d = \max(x_d, y_d) - \min(x_d, y_d) + k \times (\min(x_d, y_d))$$
+
+Where $k$ is the diagonal cost.
+
+### Tweaking
+
+[![A D&D distance Voronoi diagram](/images/post_2016_08_25/dandd_voronoi.png "A D&D distance Voronoi diagram"){width=150 height=150}](/images/post_2016_08_25/dandd_voronoi.png)
+
+Remember that the D&D distance ended up being closest to Euclidean. If
+$x_d$ and $y_d$ are equal -- that is, if we're going at exactly a
+45-degree angle -- the Euclidean distance is $x_d \times
+\sqrt(2)$. That $sqrt(2)$ is 1.41421....
+
+Last post, I mused that, since 1.5 was much closer to $sqrt(2)$ than
+either 1 (civ) or 2 (taxi), that is why D&D distance was such a good
+Euclidean approximation.
+
+But, could it be better? If we plug $k=1.4$ into our general distance
+equation, does it come closer?
+
+Well, so I wrote a script to test this. It's [up in the same repo as
+last time](https://github.com/jmelesky/voronoi_example).
+
+
+It generates a set of random points, then calculates the Euclidean
+distance from $(0,0)$ to each point. For each of the other measures,
+it calculates the same distance, and compares it to the Euclidean,
+keeping the difference. At the end, it prints out the total difference
+for each measure.
+
+[![A Voronoi diagram witk k=1.4](/images/post_2016_08_29/tweaked_voronoi.png "A Voronoi diagram with k=1.4"){width=150 height=150}](/images/post_2016_08_29/tweaked_voronoi.png)
+
+It looks a bit like this:
+
+~~~ { .bash }
+$ ./test_distances.py 
+taxi  diff: 12848
+civ   diff: 5146
+dandd diff: 3851
+other diff: 2071
+~~~
+
+As you can see, the "other" calculation ($k = 1.4$) has the least
+difference from the Euclidean distance, and so is a nice, quite
+accurate measure.
+
+1.4 is a tricky number. If you wanted to use it in a game of some
+sort, though, you could cost it out differently -- the thing that
+matters is the ratio. For example, it might cost 5 action points to
+move in a cardinal direction, but 7 action points to move
+diagonally. That would be quite true to Euclid, while nicely operating
+in our discrete movement world.
+
+Thanks for reading through, and hope it was interesting for you, too.

+ 42 - 0
content/blog/2016-09-01-trail-series-injury.md

@@ -0,0 +1,42 @@
+---
+title: The Trail Run in which I Injure Myself
+tags: running, portland trail series
+description: I finished, and didn't die, but limped most of the last half after pulling something
+---
+
+As some of you know, this summer I ran in the [Portland Trail
+Series](https://gobeyondracing.com/races/portland-trail-series/),
+which was pretty ambitious for someone who walks a fair portion of a
+5k. But it was enjoyable for the most part, even though I was the last
+finisher for four out of the five races.
+
+Yesterday was the first race of the fall season of the Trail
+Series. It's been a few weeks, though I've tried to run occasionally
+so I'm not completely unprepared.
+
+The good news is that I finished. And, true to form, finished last.
+
+The bad news is that, unlike the prior races, I injured myself,
+pulling my left hamstring a bit further than halfway into the race. I
+limped the rest of my way back.
+
+I don't mind finishing last. I don't mind injuring myself. I mean, I'm
+not keen on it, but it's something that happens and isn't going to get
+less frequent as I age, so I might as well accept it for what it is.
+
+But I was so far behind that they called me on my cell to see if I was
+still in the race.
+
+I finished a full 30 minutes behind the next racer, and they'd already
+packed up everything but the big timer that told me exactly how long
+it had been since I started.
+
+I'm trying to have a positive spin on it. I mean, even limping half
+the race my pace was still better than 20 minutes per mile, right? But
+I still feel frustrated at inconveniencing people, and the whole
+experience was just a wee bit embarassing.
+
+Next race is in two weeks, so I have time to heal up and even prep a
+bit more. Hopefully I'll be back to last place by 5 minutes again.
+
+

+ 92 - 0
content/blog/2016-09-09-resurrecting-zombies.md

@@ -0,0 +1,92 @@
+---
+title: Resurrecting Zombies
+tags: haxe, games, zombie protagonist
+description: Digging up and dusting off some decade-old code to see if it works with a months-old compiler.
+---
+
+About ten years ago, I decided to write [a flash
+game](/stuff/zombie_protagonist.html). I used unconventional
+technologies ([Haxe](http://haxe.org/) for the code, and
+[darcs](http://darcs.net/) for the source control). After tinkering
+with it for a year or so, I never touched it again (This, I fear, is
+not uncommon in the world of side projects).
+
+Until now! I've got a new site, and I'm trying to clean up some old
+stuff, so it's time to dig up the code, dust it off, and see if it
+works, and how it works.
+
+### Darcs
+
+Originally, I self-hosted the code, accessing it via using darcs over
+ssh on my server. Eventually, I used a hosting service for darcs
+(patch-tag.com). When that site folded a few years back, I failed to
+get a copy in time, and assumed that the source was lost.
+
+But, wait, I found a tarball of the source! It has no history, but at
+least it's something. Hopefully it's the most recent version.
+
+But, wait again, I found a buried source directory that actually
+included the `_darcs` information! Excellent! I had a copy of the full
+history after all.
+
+Now, I love darcs[^pijul]. I use [git](https://git-scm.com/)
+professionally, of course. It's incredibly fristrating to use git
+after being acclimated to darcs -- so many things become awkward, or
+translate poorly. Yet git is the lingua franca of modern source
+control, and to deny that is to [claim that forests don't
+exist](http://www.theatlantic.com/science/archive/2016/09/flat-earth-truthers/499322/). Therefore, I must convert to git.
+
+Darcs has some [minimal, but excellent
+documentation](http://darcs.net/Using/Convert) on exporting to
+git. Which I use. But a `git log` shows me only one commit. What's up
+with that? I want my full commit history, please. Several hours later,
+I finally just try `darcs log` in the darcs repo:
+
+~~~ {.bash}
+$ darcs log
+patch 93e98bccb6c6773f349d394eb27bf311081b0fac
+Author: jmelesky <code@phaedrusdeinus.org>
+Date:   Tue May 12 15:11:18 PDT 2009
+  * import from darcs1 repo
+~~~
+
+Well, that answers that. I apparently destroyed the history myself,
+seven years back. More's the pity. That does answer whether I'm
+dealing with an ancient darcs repo, or [a slightly less
+ancient](http://darcs.net/DarcsTwo) one.
+
+### Haxe
+
+Now that we're in a git repo, let's see if we can compile this son of
+a gun. Haxe is easy to install, but has changed quite a bit since it
+was young and edgy and called "haXe". I've got a file called
+`build.hxml`[^hxml] in my repo, but apparently haxe no longer uses
+that sort of build file.
+
+Instead, I'll start with something that's my best guess [based on
+modern compiler usage](http://haxe.org/manual/compiler-usage.html):
+
+~~~ {.bash}
+ haxe -cp . -main ZombieProtagonist -swf Zombie.swf
+./Actor.hx:47: characters 8-19 : Actor should be Void
+.... (lots more errors)
+~~~
+
+Well, I guess the language has progressed a bit. Or, I suppose, this
+is a version which never compiled. I think the former, though.
+
+More work to be done. In the meantime, the project is [up on
+github](https://github.com/jmelesky/zombie_protagonist). Tune in next
+time to see more of my continuing struggles to create a SWF file.
+
+
+
+[^pijul]: If I were to start over again today, I might be tempted by
+[pijul](http://pijul.org/), which has most of darcs' strengths, and
+few of its weaknesses. But I'm not, so I won't.
+
+[^hxml]: The [documentation for the old
+compiler](http://old.haxe.org/doc/compiler) indicate that this was
+really a thing, which is good, because the [modern
+stuff](http://haxe.org/manual/compiler-usage-flags.html) doesn't
+mention it at all. I got a bit concerned for my past sanity.

+ 90 - 0
content/blog/2016-09-10-resurrecting-zombies-part-2.md

@@ -0,0 +1,90 @@
+---
+title: Resurrecting Zombies, Part 2
+tags: coding, haxe, games, zombie protagonist
+description: Going through a bunch of compiler errors to get the old code at least a bit closer to working.
+---
+
+### `Actor should be Void`
+
+Fourteen things "should be Void". That's lots of things. It's also,
+coincidentally, the count of constructor methods that end in `return
+this;`:
+
+~~~ {.bash}
+$ grep -c "return this;" *.hx
+Actor.hx:2
+KeyboardMonitor.hx:0
+Screens.hx:12
+ZombieProtagonist.hx:0
+~~~
+
+Amazingly, the locations (e.g. "`./Actor.hx:47: characters 8-19`") all
+map, too. Shocking. Can getting this to compile be as simple as
+removing those return statements? Maybe not, but it gets us further:
+
+~~~ {.bash}
+$ haxe -cp . -main ZombieProtagonist -swf Zombie.swf
+./Actor.hx:322: lines 322-328 : Field touchedHaven should be declared with 'override' since it is inherited from superclass FollowFlee
+... (a few of these)
+~~~
+
+### Overriding
+
+According to [modern haxe
+practices](http://haxe.org/manual/class-field-overriding.html), if
+you're overriding a parent method, you have to explicitly define the
+method with the `override` keyword.
+
+Part of me hoped it would be this simple. The only compiler errors
+reported at the start were of these two varieties. There were a bunch,
+but just thosw two categories. Unfortunately, that was apparently just
+a first parsing pass.
+
+~~~ {.bash}
+$ haxe -cp . -main ZombieProtagonist -swf Zombie.swf
+./Actor.hx:270: characters 19-25 : Local variable startx used without being initialized
+~~~
+
+Two interesting things here: 1- apparently variable declaration rules
+have changed, and 2- only one error was spit out. That latter thing is
+ominous, and suggests to me that I'm about to dive into a very long
+back and forth between compiler and editor[^ide].
+
+### Bug Hunt
+
+Well, that turned out not to be as bad as feared. Turn a couple of:
+
+~~~ {.haxe}
+        var startx;
+        var starty;
+~~~
+
+... into ...
+
+~~~ {.haxe}
+        var startx = 0;
+        var starty = 0;
+~~~
+
+... which got rid of that error entirely. Then came:
+
+~~~ {.haxe}
+./Screens.hx:44: characters 8-14 : You cannot assign some super class vars before calling super() in flash, this will reset them to default value
+~~~
+
+That meant crawling through all the places I was calling `super()` and
+moving them to before any attribute assignments. That was confined
+solely to constructor methods.
+
+Once that was done? It compiled!
+
+... and the SWF was a whitescreen.
+
+Alright, then. Back to the drawing board. Now that it compiles, I
+supposed I'll have to actually figure out how it works, in order to,
+you know, make it work.
+
+
+[^ide]: At this point, I'm tempted to start using a haxe IDE to help
+me along. I'm pretty solidly in the emacs camp, though, so it's going
+to take a little more, yet.

+ 103 - 0
content/blog/2016-10-05-union-all-views.md

@@ -0,0 +1,103 @@
+---
+title: Partitioning Without Partitions
+tags: SQL
+description: I look at an old SQL hack for partitioning called "UNION ALL views".
+---
+
+One of the common modern features of relational databases is the ability to partition tables. "Partition" can mean a few different things, but in this case, I'm talking about the common use-case of 1- taking a large table, 2- splitting it up into smaller tables, but 3- still accessing those tables as if they were a single table. There are some benefits to partitioning, particularly around maintenance tasks. Smaller tables mean swifter table scans, faster table operations, and faster histogram generation, not to mention the opportunity for more widely varied histograms for your data.
+
+As mentioned, though the feature is *relatively* modern. Oracle added it in 1997, SQL Server in 1998 (sort of), and later in 2005 (for real). DB2 didn't add it till 2007 (seriously). PostgreSQL added table inheritance (which is commonly used to implement partitioning) somewhere prior to 2000.
+
+But the need predates 1997. And, well, DB2 users were out in the cold until less than a decade ago. What did you do if you needed to handle Big Data[^bigdata] in 1995?
+
+### UNION [ALL]
+
+As it turns out, enterprising hackers can do all sorts of amazing things. See, there's a SQL operator called `UNION`, which is a name that absolutely makes sense if you think about data in a very specific way.
+
+Remember [sets](https://en.wikipedia.org/wiki/Set_(mathematics))? Maybe as a data structure, maybe from formal logic, maybe from advanced math? Well, if you think about the results of a SQL `SELECT` as a set, then the [`UNION`](https://en.wikipedia.org/wiki/Union_(set_theory)) operator does exactly what you think it would: it takes the results of two different `SELECT` statements and delivers the set of unique results[^sets].
+
+For example[^postgres]:
+
+~~~ {.sql}
+=# SELECT generate_series(1,5);
+ generate_series 
+-----------------
+               1
+               2
+               3
+               4
+               5
+=# SELECT generate_series(1,5) UNION SELECT generate_series(4,8);
+ generate_series 
+-----------------
+               1
+               2
+               3
+               4
+               5
+               6
+               7
+               8
+~~~
+
+Note that this is a real set-like union -- the numbers 4 and 5 were part of both `SELECT` results, but only showed up once in the output.
+
+.... Which might not be what you want. Often times, maybe even most often, you want all of the results. That's what the `UNION ALL` operator does:
+
+~~~ {.sql}
+=# SELECT generate_series(1,5) UNION ALL SELECT generate_series(4,8);
+ generate_series 
+-----------------
+               1
+               2
+               3
+               4
+               4
+               5
+               5
+               6
+               7
+               8
+~~~
+
+
+Now we're talking. That may not have set-theoretical utility, but it's much more useful for the pragmatics of table partitioning.
+
+### CREATE VIEW foo
+
+With `UNION ALL`, partitioning actually becomes relatively straightforward (if still hacky and a bit brittle). You just create your partitions as separate tables, and then slap them together in a big view:
+
+~~~ {.sql}
+=# CREATE TABLE foo2014 (d date, a int, b text);
+=# CREATE TABLE foo2015 (d date, a int, b text);
+=# CREATE TABLE foo2016 (d date, a int, b text);
+=# CREATE VIEW foo AS
+    SELECT * FROM foo2014
+      UNION ALL
+    SELECT * FROM foo2015
+      UNION ALL
+    SELECT * FROM foo2016;
+~~~
+
+Et, voila! You can now select data from one place, and have your DB search a bunch of places. To play with a live demo, [check this sqlfiddle](http://sqlfiddle.com/#!15/953c5/2) (and consider donating to SQL Fiddle).
+
+Now, this is hacky. Unless you can hang triggers on your views, you probably need to send all of your `INSERT` and `UPDATE` traffic to the partitions directly, rather than to the "table" itself. And your database may not be smart enough to avoid looking at all of your partitions for each query[^constraintexclusion].
+
+And it's brittle. I do hope you automated partition creation, because if your columns are inconsistently named or typed, then you'll have problems. And each time you need a new partition, you have to re-create the view.
+
+But it works. And it works in anything that conforms to SQL99. It even works in sqlite (though its usefulness there is debatable, since sqlite databases are a single file on-disk anyway).
+
+There's not much use for this technique now that we have more mature and robust partitioning capabilities available to us, though.
+
+Tomorrow I'll walk through some non-partitioning uses for these techniques that might be a little more relevant.
+
+
+
+[^bigdata]: Where "Big" is relative to the time. We're talking gigadata, not teradata, and certainly not petadata.
+
+[^sets]: SQL99 defines `INTERSECT` and `EXCEPT` operators, as well, for all of your tables-as-sets needs.
+
+[^postgres]: Using PostgreSQL (specifically v9.5.4). I cheated slightly and used `ORDER BY` clauses to actually generate the results, for readability purposes. The SQL as-written will return results in a potentially-unstable order.
+
+[^constraintexclusion]: Most are, but sometimes require extra configuration. And all the ones I know of rely on constraints on each partition table, and possible mirrors of those constraints in each `SELECT` clause in the view.
+

+ 94 - 0
content/blog/2016-10-07-union-view-tricks.md

@@ -0,0 +1,94 @@
+---
+title: Uses for UNION ALL and Views
+tags: SQL
+description: A few things you can pull off with some SQL that are not hacks to get partitioning
+---
+
+Right, so [yesterday](/posts/2016-10-05-union-all-views.html)[^yesterday], I talked about one of the cooler historical SQL hacks out there. Turns out you can (perhaps somewhat incompletely) replicate useful features purely with pure SQL99 (and pure SQL92[^sqlold]). There are, in fact, several additional fun ways to use the `UNION` operator in conjunction with views, and I'd like to talk about them.
+
+### The Now Partition
+
+Let's say you have a heavy-write table that is also heavy-read. The writes come often, but you also regularly need to run reports against that data. And your customers are pretty picky about timeliness, so you can't just run the reports on a batch-populated, heavily-indexed table. But putting lots of indexes on this table would bring the insert/update time far below what you need to keep up.
+
+Your customers are complaining, your devs are helpess. What are you to do?
+
+Imagine combining the fast-update speed of a small, unindexed table with the fast querying of a thoroughly-indexed, batch-populated table! The possibilities would be *endless*.
+
+All this is possible with a pattern I like to call "The Now Partition".
+
+The concept is straightforward: send your insert traffic to one table, and have a batch process move the data transactionally to a queryable table. That much seems obvious. The secret sauce[^obvious] is using a view to `UNION ALL` those two tables together. Something like:
+
+~~~ {.sql}
+=# CREATE TABLE foo_20161005 (d date, a int, b text);
+=# CREATE INDEX ON foo_20161005 (a);
+=# CREATE INDEX ON foo_20161005 (b);
+=# CREATE TABLE foo_20161006 (d date, a int, b text);
+=# CREATE INDEX ON foo_20161006 (a);
+=# CREATE INDEX ON foo_20161006 (b);
+=# CREATE TABLE foo_now (d date, a int, b text);
+=# CREATE VIEW foo AS
+  SELECT * FROM foo_20161005
+    UNION ALL
+  SELECT * FROM foo_20161006
+    UNION ALL
+  SELECT * FROM foo_now;
+~~~
+
+Just make sure all your OLTP traffic hits `foo_now`, and you're all set. The batch transfer should be straightforward, though I advise constraining your `DELETE`. Unconstrained, you might run into some race conditions and delete more than you bargained for.
+
+This may look familiar if you've read yesterday's post. Don't worry, the next one's a bit further afield.
+
+
+### The Lazy Archive
+
+Let's imagine a different scenario. You need to be able to query all of history, but you only care that it's fast for the past year. Anything further back can take hours or days. Materialized views (or manual summary tables, depending on your RDBMS's capabilities) provide the speed you need.
+
+So what's the problem? Your data is complicated. You need a few dozen different materialized views to accommodate all of your report use cases. And you've got decades of data archived -- that's not only a whole lot of storage space for all those materialized views, maintaining them takes more effort and more time.
+
+The solution? Have materialized views for the last year, and keep the archived facts unsummarized. And (drumroll) connect them using `UNION ALL` in views!
+
+Something like:
+
+~~~ {.sql}
+=# CREATE VIEW foo_by_a AS
+  SELECT rundate, a, sum(b), sum(c), ...
+      FROM foo
+      WHERE rundate < '2016-01-01'
+      GROUP BY rundate, a
+    UNION ALL
+  SELECT rundate, a, b, c, ...
+      FROM foo2016_summ_by_a;
+
+=# CREATE VIEW foo_by_b AS
+  SELECT rundate, b, sum(a), sum(c), ...
+      FROM foo
+      WHERE rundate < '2016-01-01'
+      GROUP BY rundate, b
+    UNION ALL
+  SELECT rundate, b, a, c, ...
+      FROM foo2016_summ_by_b;
+
+=# ....
+~~~
+
+Boom! There you go.
+
+Unlike the earlier examples, we're not just slapping similar tables together. Instead, we're slapping together the results of two queries that happen to have the same column layout.
+
+Of course, if you look back over all the other examples, that's what we're doing everywhere. `SELECT *` and "the table" is an easy difference to gloss over. It's good to remember occasionally that `UNION` is an operator that operates on result sets, though, not tables. And that makes it very flexible.
+
+That flexibility is what we're exploiting here, creating an on-the-fly set of summarized data in response to those infrequent queries on old data. And it should work without slowdown on queries in the past year[^slowdown].
+
+Ultimately, this is just one trick, but it's such a useful one. If you find yourself in a position where you need your data in two (or more) different layouts, and want the best of both worlds, well `UNION ALL` is exactly the tool for the job.
+
+Let me know if you have any other `UNION ALL` patterns you like!
+
+
+
+[^yesterday]: Okay, so it was two days ago. Posterity will forgive me. Unless posterity somehow can't read footnotes, and decides that "yesterday" could mean "two days ago" in the early 21st century. Or that I didn't know math, dates, or date math.
+
+[^obvious]: Which, let's be honest, is probably also a bit obvious. I mean, I mentioned at the top of the page that I was going to talk about `UNION` and views, and you're all clever people, so ....
+
+[^sqlold]: At least as far back as SQL-92. I haven't been able to track down a copy of SQL-89 or SQL-86 to check the existence of UNION.
+
+[^slowdown]: Depending on your RDBMS. Like yesterday's caveat about configuration, you may need to tweak some settings for your planner to be smart about your old data. And there may be systems with very poor optimizers that end up reading in your entire archive table every query. I recommend migrating away from such systems (at least for your reporting needs).

+ 44 - 0
content/blog/2017-07-31-why-learn-rust.md

@@ -0,0 +1,44 @@
+---
+title: Why Am I Learning Rust?
+tags: rustlang
+description: A short note about skills and practice
+---
+
+Professionally speaking, I'm no longer a software developer, and haven't been for a few years[^DBA]. Indeed, my time on the operations side of the aisle has done nothing but increase my preference towards proven, off-the-shelf solutions. Not Invented Here? Great, sign me up!
+
+Yet, I insist on doing things like brushing up on my very rusty Haskell to put together this website (see [the "About this blog" section](/about.html#about-this-blog)), and, more recently, trying to learn [Rust](http://rust-lang.org/). Why?
+
+Two reasons, really. First, and most important, it's ... well, I was going to say "fun", but I think it's probably better described as "good". I enjoy it, even when it's frustrating. There's a rewarding fulfillment in getting something to work. There's rewarding engagement when something isn't working (at least to a certain point).
+
+Second, and possibly more important than that most important thing, it's good practice. Well, it's good practice when it's good. Actually, let's stop using the word "good", and instead use the word "focused", since I'm really talking about the concept of focused practice.
+
+Since I don't code professionally the way that I used to, I need to keep up some practice just to slow the backslide of losing that skill. That's something that's becoming more present in my mind as I age and watch the skills of my youth atrophy[^atrophy]. And that's fun and rewarding enough, but focused practice is something else. That's when you practice something that you're weak at, with the aim of improving your skill. One way to tell the difference between maintenance pracitce and focused practice is the ratio of frustration to fulfillment -- focused practice usually means much higher amounts of frustration, and more time not feeling like you know what you're doing. That's the point, really.
+
+### Learning stuff I already know
+
+Learning Haskell wasn't focused practice for me. I was already familiar with the functional and type-driven aspects of it from my experience with the SML family. Laziness required some work, and purity mostly felt like an inconvenience. I wasn't learning Haskell to become a better programmer, I was learning it for practical reasons; it was the typeful functional language with a community and a future[^ocaml].
+
+Setting up this website was likewise for fun and practical reasons, rather than for improvement. I wanted a website again, and I didn't want a particularly dynamic one, and I thought wrestling with Hakyll would be fun. As it turns out, it was (even if I post infrequently enough to need to check my shell history every time I publish).
+
+Not too long ago, I picked up [an old project](https://github.com/jmelesky/omwllf) and tinkered it into a working state. I did some parsing of binary files, and some fancy python generator tricks. I worked with python 3. And none of it was much of a stretch.
+
+### Learning stuff I don't
+
+Rust, on the other hand, is a stretch. There may not seem to be much in common between Python and Haskell, but there's one rather big thing: they're both operating at very high levels of abstraction. I don't have to think about how threads work, or the in-memory representation of my data, or any fun stuff like that. It's all taken care of under the covers.
+
+Rust, on the other hand, is really a systems-level language, despite some of its very fancy features. I'm having to become conversant in words like `&` and `*` for the first time since school. The [intro documentation](https://doc.rust-lang.org/book/second-edition/) culminates in building a thread pool library from the thread spawning and message passing primitives available in the standard library.
+
+This is not stuff that I'm good at.
+
+So I'm trying.
+
+
+
+
+
+[^DBA]: I'm currently a Database Administrator and a manager of DBAs. Hence my occasional [article about SQL](/tags/SQL.html).
+
+[^atrophy]: Often for the best, to be sure. I'm certain, for example, that my once lauded ability to survive on a diet of Twinkies and warm Coca-Cola is an adaptation that belongs in the past. And, lest you worry, I continue to maintain important skils like dreadful punnery and recalling commercial jingles of my youth.
+
+[^ocaml]: And it was cooler than ocaml. Ocaml also came with the problem that I couldn't help but compare it to SML, all the time.
+

BIN
content/images/mthood.jpg


BIN
content/images/post_2016_08_25/base.png


BIN
content/images/post_2016_08_25/civ_voronoi.png


BIN
content/images/post_2016_08_25/dandd_voronoi.png


BIN
content/images/post_2016_08_25/euclid_voronoi.png


BIN
content/images/post_2016_08_25/euclidean.png


BIN
content/images/post_2016_08_25/manhattan_voronoi.png


BIN
content/images/post_2016_08_25/odd_dandd_voronoi.png


BIN
content/images/post_2016_08_25/taxi_voronoi.png


BIN
content/images/post_2016_08_29/other_path.png


BIN
content/images/post_2016_08_29/taxi_path.png


BIN
content/images/post_2016_08_29/tweaked_voronoi.png


+ 47 - 0
content/pages/about.md

@@ -0,0 +1,47 @@
+---
+Title: About
+Date: 2016-08-28
+Modified: 2023-09-20
+Author: john melesky
+---
+Hello, I'm John.
+
+I grew up in the northeast US, migrated to Chicago, then more recently
+to Portland, OR, where I've been for the better part of the last
+decade.
+
+![Mt Hood and Trillium Lake, by flickr user [smik67](https://www.flickr.com/photos/smik67/6223213433/), used in accordance with cc-by-nc-md]({static}/images/mthood.jpg)
+
+This is the most recent version of my website, which has a long and
+storied history, most of which is best left forgotten. We forget with
+such difficulty these days.
+
+Some of my earliest memories are of working with my older sister to
+type BASIC programs into our TI 99/4A from a [*Family
+Computing*](https://archive.org/details/family-computing) magazine. We
+would trade off who was reading out and who was typing in. The
+programs were mostly small games, or the sort of short, rotating
+animation that would later make a decent GIF.
+
+Enchanted, rather than deterred, by those early programming
+experiences, I've tinkered with software ever since, most of that time
+in some professional capacity. Let me know if you're [interested in
+working with me](/work.html), or for that matter if you're [interested
+in some small things I've built](/stuff.html). I've also been known to
+[speak at conferences](/talks.html), if that's your thing.
+
+If you wish to get in touch with me, then you can email me at an
+address comprised of the word "site", an at sign (@), and the domain
+name this site is hosted on (a ".org" domain named after a longtime
+pseudonym, "phaedrusdeinus"). I don't have a strong history of
+correspondence, but I will try to get back to you.
+
+
+### About this blog
+
+This blog is generated using [Pelican](https://getpelican.com/), a
+static site generator.
+
+I used emacs, git, [the gimp](https://www.gimp.org/), and
+[gogs](http://gogs.io/) in the construction and maintenance of the
+site.

+ 34 - 0
content/pages/stuff.md

@@ -0,0 +1,34 @@
+---
+title: Stuff I've cobbled together
+date: 2016-09-01
+author: john melesky
+---
+
+I've been a programmer for longer than I've been a professional
+programmer. And most of the stuff I've produced as a professional is
+sadly not available to share.
+
+That said, some stuff is just from me, unprofessionally. Not all of
+that is *worth* sharing, mind you, and even less survived the ravages
+of time. Still, here's some stuff.
+
+  - [Zombie Protagonist](/stuff/zombie_protagonist.html) - My first (and
+    thusfar only) flash game, where you are a person trying to save
+    people from zombies. Possibly influenced by [Shaun of the
+    Dead](https://en.wikipedia.org/wiki/Shaun_of_the_Dead).
+
+  - [tabular_cleanse.py](https://github.com/jmelesky/tabular_cleanse) - 
+    A very simple, niche-use, but incredibly-handy-if-you-need-it
+    script for turning tabs into spaces in a way that conforms to the
+    Python whitespace standard.
+
+  - [My personal slideshow
+    builder](https://github.com/jmelesky/slidemaker) - This was my
+    go-to for conference presentations for years. Conference computers
+    always had a web browser available, and this was much
+    lighter-weight than any of the other html-based slideshow kits at
+    the time.
+
+I'll add more stuff to this list as I dust things off.
+
+

+ 57 - 0
content/pages/talks.md

@@ -0,0 +1,57 @@
+---
+title: Talks and Presentations
+---
+
+I've given a number of talks at conferences and other meetups over the
+years. Here are some of the materials for a few of them. My
+presentations tend to be less slide-dependent than some, so these
+materials are not guaranteed to be useful. If you're certain I've
+given a talk which you don't see here, but want access to the
+materials, let me know -- I may have them, I may not.
+
+  - I've given several talks (some multiple times) on machine learning:
+
+    - Earlier this century, bayesian classifiers were all the rage. I
+      gave a talk at a few BarCamps [explaining Bayes'
+      Theorem](talks/bayes_theorem/) (an old S5 presentation, so
+      functionality not guaranteed).
+
+    - Another popular talk was "[Write your own Bayesian
+      Classifier!](talks/your-own-bayes/)", which I'm ashamed to say I
+      sometimes tried to do in a 30-minute slot.
+
+    - I've given [a brief survey](talks/ml-primer) on machine
+      learning concepts and techniques.
+
+    - I once gave a [lightning talk about SVMs](talks/svm-lightning/).
+
+  - I've given several talks on PostgreSQL. Most talks I've collected
+    here have videos of the presentations, rather than slides. The
+    quality of the videos varies, sorry.
+
+    - A few years back I gave a talk on [using postgres_fdw to
+      federate queries](https://www.youtube.com/watch?v=UZ1eqzOeIg0).
+
+    - I did a bit of a [deep dive into postgres
+      partitions](https://www.youtube.com/watch?v=Hl3FXv8uBUo).
+
+    - A while back, I gave a talk about the current state of [using
+      postgres in cloud
+      environments](https://www.youtube.com/watch?v=4fIhhn74mtA). Cloud
+      environments have changed significantly in the intervening time,
+      so I can no longer stand by my recommendations in this talk.
+
+    - For the local PUG, I gave a somewhat tongue-in-cheek talk about
+      [two locking problems I had
+      encountered](talks/locking_war_stories/).
+
+  - I've given a number of other talks, some for local python user
+    groups, some about other dynamic language issues. For a while, the
+    Portland Python User Group had a "module of the month"
+    feature. Here are two that I presented:
+
+    - A comparison of [profile and cProfile](talks/profile-cprofile/)
+
+    - A brief overview of the [multiprocessing
+      library](talks/multiprocessing)
+

+ 31 - 0
content/pages/work.md

@@ -0,0 +1,31 @@
+---
+title: Me, Professionally
+---
+
+I've been in the industry since before the dot-com boom of the late
+nineties (I almost said "late twentieth century" there, but that makes
+me sound much older than I'd like to be.
+
+I have a long history of working with large, often unstructured data,
+starting with the set of member pages at
+[Tripod.com](http://tripod.lycos.com/). I have a background in machine
+learning, mostly around document classification, though I'm a quite
+behind the current state of the art at this point. I've written a
+number of reporting systems, and am comfortable enough with databases
+that I've been a DBA for the past few years.
+
+Obviously, as a DBA, I'm operationally minded. As a developer, I tend
+to be that way, as well -- I'm concerned with robustness of the
+systems I work on, and think ahead to reduce possible failure modes in
+production.
+
+I'm comfortable in contributing leadership positions, and also have
+some experience in management.
+
+I am not actively seeking new employment, though I'm not averse to
+hearing about new, interesting opportunities. Here is a [relatively
+recent resume](/resume_jmelesky.pdf) (PDF format), and you may also be
+interested in [talks I've given at conferences](/talks.html).
+
+
+

+ 39 - 0
pelicanconf.py

@@ -0,0 +1,39 @@
+AUTHOR = 'john melesky'
+SITENAME = "john's corner of the web"
+SITEURL = ''
+
+PATH = 'content'
+ARTICLE_PATHS = ['blog']
+ARTICLE_SAVE_AS = '{date:%Y}/{slug}.html'
+ARTICLE_URL     = '{date:%Y}/{slug}.html'
+
+TIMEZONE = 'US/Pacific'
+
+DEFAULT_LANG = 'en'
+
+THEME = '/home/jmelesky/external_src/pelican-sober'
+
+PLUGINS = ['render_math']
+
+# Feed generation is usually not desired when developing
+FEED_ALL_ATOM = None
+CATEGORY_FEED_ATOM = None
+TRANSLATION_FEED_ATOM = None
+AUTHOR_FEED_ATOM = None
+AUTHOR_FEED_RSS = None
+
+# Blogroll
+LINKS = ()
+# LINKS = (('Pelican', 'https://getpelican.com/'),
+#          ('Python.org', 'https://www.python.org/'),
+#          ('Jinja2', 'https://palletsprojects.com/p/jinja/'),
+#          ('You can modify those links in your config file', '#'),)
+
+# Social widget
+SOCIAL = (('Akkoma', 'https://tinylad.social/jmelesky'),
+          ('LinkedIn', 'https://www.linkedin.com/in/jmelesky/'),)
+
+DEFAULT_PAGINATION = 10
+
+# Uncomment following line if you want document-relative URLs when developing
+#RELATIVE_URLS = True

+ 21 - 0
publishconf.py

@@ -0,0 +1,21 @@
+# This file is only used if you use `make publish` or
+# explicitly specify it as your config file.
+
+import os
+import sys
+sys.path.append(os.curdir)
+from pelicanconf import *
+
+# If your site is available via HTTPS, make sure SITEURL begins with https://
+SITEURL = 'https://phaedrusdeinus.org'
+RELATIVE_URLS = False
+
+FEED_ALL_ATOM = 'feeds/all.atom.xml'
+CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml'
+
+DELETE_OUTPUT_DIRECTORY = True
+
+# Following items are often useful when publishing
+
+#DISQUS_SITENAME = ""
+#GOOGLE_ANALYTICS = ""

+ 143 - 0
tasks.py

@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+
+import os
+import shlex
+import shutil
+import sys
+import datetime
+
+from invoke import task
+from invoke.main import program
+from invoke.util import cd
+from pelican import main as pelican_main
+from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
+from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
+
+OPEN_BROWSER_ON_SERVE = True
+SETTINGS_FILE_BASE = 'pelicanconf.py'
+SETTINGS = {}
+SETTINGS.update(DEFAULT_CONFIG)
+LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
+SETTINGS.update(LOCAL_SETTINGS)
+
+CONFIG = {
+    'settings_base': SETTINGS_FILE_BASE,
+    'settings_publish': 'publishconf.py',
+    # Output path. Can be absolute or relative to tasks.py. Default: 'output'
+    'deploy_path': SETTINGS['OUTPUT_PATH'],
+    # Remote server configuration
+    'ssh_user': 'jmelesky',
+    'ssh_host': 'phaedrusdeinus.org',
+    'ssh_port': '22',
+    'ssh_path': '/home/jmelesky/site/pd.org',
+    # Host and port for `serve`
+    'host': 'localhost',
+    'port': 8000,
+}
+
+@task
+def clean(c):
+    """Remove generated files"""
+    if os.path.isdir(CONFIG['deploy_path']):
+        shutil.rmtree(CONFIG['deploy_path'])
+        os.makedirs(CONFIG['deploy_path'])
+
+@task
+def build(c):
+    """Build local version of site"""
+    pelican_run('-s {settings_base}'.format(**CONFIG))
+
+@task
+def rebuild(c):
+    """`build` with the delete switch"""
+    pelican_run('-d -s {settings_base}'.format(**CONFIG))
+
+@task
+def regenerate(c):
+    """Automatically regenerate site upon file modification"""
+    pelican_run('-r -s {settings_base}'.format(**CONFIG))
+
+@task
+def serve(c):
+    """Serve site at http://$HOST:$PORT/ (default is localhost:8000)"""
+
+    class AddressReuseTCPServer(RootedHTTPServer):
+        allow_reuse_address = True
+
+    server = AddressReuseTCPServer(
+        CONFIG['deploy_path'],
+        (CONFIG['host'], CONFIG['port']),
+        ComplexHTTPRequestHandler)
+
+    if OPEN_BROWSER_ON_SERVE:
+        # Open site in default browser
+        import webbrowser
+        webbrowser.open("http://{host}:{port}".format(**CONFIG))
+
+    sys.stderr.write('Serving at {host}:{port} ...\n'.format(**CONFIG))
+    server.serve_forever()
+
+@task
+def reserve(c):
+    """`build`, then `serve`"""
+    build(c)
+    serve(c)
+
+@task
+def preview(c):
+    """Build production version of site"""
+    pelican_run('-s {settings_publish}'.format(**CONFIG))
+
+@task
+def livereload(c):
+    """Automatically reload browser tab upon file modification."""
+    from livereload import Server
+
+    def cached_build():
+        cmd = '-s {settings_base} -e CACHE_CONTENT=true LOAD_CONTENT_CACHE=true'
+        pelican_run(cmd.format(**CONFIG))
+
+    cached_build()
+    server = Server()
+    theme_path = SETTINGS['THEME']
+    watched_globs = [
+        CONFIG['settings_base'],
+        '{}/templates/**/*.html'.format(theme_path),
+    ]
+
+    content_file_extensions = ['.md', '.rst']
+    for extension in content_file_extensions:
+        content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension)
+        watched_globs.append(content_glob)
+
+    static_file_extensions = ['.css', '.js']
+    for extension in static_file_extensions:
+        static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension)
+        watched_globs.append(static_file_glob)
+
+    for glob in watched_globs:
+        server.watch(glob, cached_build)
+
+    if OPEN_BROWSER_ON_SERVE:
+        # Open site in default browser
+        import webbrowser
+        webbrowser.open("http://{host}:{port}".format(**CONFIG))
+
+    server.serve(host=CONFIG['host'], port=CONFIG['port'], root=CONFIG['deploy_path'])
+
+
+@task
+def publish(c):
+    """Publish to production via rsync"""
+    pelican_run('-s {settings_publish}'.format(**CONFIG))
+    c.run(
+        'rsync --delete --exclude ".DS_Store" -pthrvz -c '
+        '-e "ssh -p {ssh_port}" '
+        '{} {ssh_user}@{ssh_host}:{ssh_path}'.format(
+            CONFIG['deploy_path'].rstrip('/') + '/',
+            **CONFIG))
+
+
+def pelican_run(cmd):
+    cmd += ' ' + program.core.remainder  # allows to pass-through args to pelican
+    pelican_main(shlex.split(cmd))