Flat Earth Catalogue


Scripture index to Episcopal (US) Daily Office Lectionary (1979)
I went looking and couldn't quite find one of these, so I took the closest thing and massaged it into shape. It's not perfect, but it should be handy. Any kind of reuse is fine by me.


Cast iron chemistry
So, treating your cast iron. The virtues of flaxseed oil for seasoning cast iron have been much discussed, and by experiment I confirm that the stuff is awesome. This post explores some ways to make it more awesome, which I have yet to try.

Washing your flaxseed oil. Food-grade flaxseed oil will contain the lonnnnng polyunsaturated triglycerides that we want, but also other stuff: mucilage, phospholipids, flavorful short-chain fatty acids. They smoke easily but don't help with polymerization. In fact, they retard it and create gumminess, skins, and such. They conduce to rancidity in the unused oil, too. We want them out.

There are refined flaxseed oils manufactured for painters and woodshops, but I wouldn't trust my food to them, so we're going to have to do this ourselves.

Ethanol washing is very effective. 1 part each oil and 40% ethanol will emulsify beautifully and the ethanol will dissolve so much crap out of the oil. To break the emulsion, add 4 parts water, agitate, and settle overnight. You can give it a rinse or two with water afterward. This is better than aqueous-only washing in that it's less laborious and it does not pre-polymerize the oil as much, so it spreads thinner and doesn't cure as fast.

Washing with brine pre-polymerizes more than plain water does. Extended contact with the dissolved short-chain fatty acids pre-polymerizes too. Painters seek these. Ironmongers should not. Thin coats, baby. Thin coats.

To help with the mixing, you can add inert ceramic chips or clean coarse silica (pool filter sand). After separating layers you may need to knock or sharply twist the container to shake loose any trapped globules. If the oil is turbid at the end because of suspended water droplets, freeze/thaw, time, or CAUTIOUS heating will clarify it. You don't want water to pool on the bottom of a hot pan under oil.

Prepping your iron. Scrub off whatever will scrub off. Electrolysis would be awesome if you can arrange it, but I was unable to get a suitable current source.

Your main problem is red rust, which is ferric oxide or iron III oxide, because it hydrates, expands, and flakes off, exposing more surface to oxidation. Black oxide or magnetite is a mixture of iron II and iron III oxides, and it's a sweet deal. Dimensionally stable, and a great surface for your seasoning oil to bind to. Unfortunately, converting red oxide to black oxide is typically done with reducing agents like hydrogen gas or carbon monoxide, neither of which I care to dick around with, and oxygen tends to snap up these reducing agents anyway unless you exclude it, which is not often an option for the humble kitchen chemist. If by some chance you can exclude oxygen, even water is reductive enough to do the job (well, steam anyway; it has to be pretty hot).

All that said, I'll stick with the recommendation to have the bare iron hot when it receives the first coat of oil. Below the oil's ignition point, please, but hot. It just might reduce some red oxide to black, and it just might drive some water of hydration out of the ferric oxide that's left, so sure, go for it.

But another attractive option is to phosphatize the bare iron. Very good anti-rust coating, also a great substrate for the seasoning oil. Here we do want iron III, in solution with phosphoric acid. When the acid hits bare iron, electrons from the metal form gaseous hydrogen with protium from the acid, carrying both away. So locally the pH rises. This causes FePO3 to come out of solution and coat the metal.

OK, how do we get food grade iron III ions? Iron supplements usually contain iron II sulfate. We can bring it up to III with hydrogen peroxide, plus an acid (phosphoric would be the logical choice) to take up hydroxide ions. How to get rid of the sulfate? Maybe we can salt it out with calcium, but I'm not sure. Food-grade calcium carbonate (clean eggshells) will dissolve in an excess of phosphoric acid to give dissolved calcium phosphate. Calcium sulfate will not dissolve in water. But is there a pH where calcium phosphate is soluble but calcium sulfate is not? My chem is too long ago to tell me. You can get dissolved calcium in plain water with acetate, nitrate, or chloride, but that's just trading one anion for another. "Monocalcium phosphate" (calcium biphosphate IIRC) is soluble (2 g solute / 100 ml solvent) and is used as a food additive, so that's a possibility.


Plover steno via hardware Dvorak
If you use a hardware Dvorak keyboard and you're using Plover to start learning machine steno, these are the key assignments you will need:

  • # 1 2 3 4 5 6 7 8 9 0 [ ]
  • S- ' a
  • T- ,
  • K- o
  • P- .
  • W- e
  • H- p
  • R- u
  • A- j
  • O- k
  • * yfid
  • -E b
  • -U m
  • -F g
  • -R h
  • -P c
  • -B t
  • -L r
  • -G n
  • -T l
  • -S s
  • -D /
  • -Z -
  • no-op \ = : q x w v z
  • arpeggiate



hard-won LaTeX tabular knowledge
No, you can't put your \label{tbl:whatever} after the table contents. If it's inside the tabular environment, it'll cock up the rendering of the bottom of your table, and if it's outside tabular (still inside table) you'll get refs to the section it's in, not its own table number. You have to put it right at the start of the tabular. And also, you have to put \centering right after your \begin{table} if you caption, because captions are centered and you'll look like a dope. LaTeX is user-hostile like that.

Still beats Microsoft Word by a country mile, though.


hard-won perl split knowledge
So ($a,$b,$c) = split '_', 'foo_bar__underscore_party_' doesn't work like you think, i.e. 'foo' eq $a and 'bar' eq $b and '_underscore_party_' eq $c. You know why? Because splitting into a list of length N is equivalent to a LIMIT argument of ... N+1. I.e. ($a,$b,$c) = split '_', 'foo_bar__underscore_party_', 4 which gets you 'foo' eq $a and 'bar' eq $b and '' eq $c. And it throws the 4th split away. UGH.


hard-won Chrome sync knowledge
Meta-meta-update: Now things again don't work the way I said they did. I'm pretty sure Google's hardward has learned of good and evil, and chosen the latter.

Meta-update: now things again work the way I said they did when I wrote this an hour ago. Eye. Roll.

Update: now nothing works the way it did when I wrote this half an hour ago. I dunno. As long as my two machines aren't turning each other's extensions on and off, I'm happy.

Enabling/disabling a Chrome extension syncs or not depending whether you have "sync settings" on, not whether you have "sync extensions" on. Syncing extensions just literally means having them available or not.

Meanwhile, even if you have "sync settings" on, settings for syncing do not sync; they are per-machine.

Hard-won non-administrator, Sierra Mac, on a Windows fileshare, Chrome user knowledge

Chrome hangs while quitting: Kill the hung processes. Restart Chrome. Sign out of it. Open and close a couple times as the default user. Now will signing back in work? Let's see.

Sierra no longer believes in the right-click workaround to shut down "Are you sure you want to open it?": defaults write com.apple.LaunchServices LSQuarantine -bool false and log out and in again.

"Keep in Dock" doesn't work: delete, rename, or move ~/Library/Preference/com.apple.dock.plist and restart. Congratulations, you have a shiny new default dock full of crapware! Now fix all the crap! Also, try putting program in Dock by dragging the actual app icon into it, rather than by starting it and right-clicking your way to "Keep in Dock".

Centrally managed password changes, leaving Apple-specific keychains behind: Applications/Utilities/Keychain\ Access.app and select "login" from the list of keychains. Edit>>"Change Password for Keychain 'login'" and fill out the old and new passwords.



log likelihood ratio explained in Perl
sub llr { # could be optimized to save storage, at cost of readability
  my ($both, $one, $other, $all) = @_;
  # complete a crosstab w/ marginals
  my $one_not_other = $one - $both;
  my $other_not_one = $other - $both;
  my $not_one = $all - $one;
  my $not_other = $all - $other;
  my $neither = ($all + $both) - ($one + $other);
  # expected counts, from marginals, assuming independence
  my $exp_both = ($one * $other) / $all;
  my $exp_one_not_other = ($one * $not_other) / $all;
  my $exp_other_not_one = ($other * $not_one) / $all;
  my $exp_neither = ($not_one * $not_other) / $all;
  # score the difference from independence
  return 2 * (
    $both * log($both/$exp_both) +
    $one_not_other * log($one_not_other/$exp_one_not_other) +
    $other_not_one * log($other_not_one/$exp_other_not_one) +
    $neither * log($neither/$exp_neither)
} # counts ($both, $one, $other, $all) -> log-likelihood ratio

Depending on your data, it's possible for expected values to be zero, which blows up when you try and divide by them. To be exact, you'll have problems if $one == 0 or $other == 0 or $one == $all or $other == $all. A rather ugly fix is to increment all the numerators and denominators before dividing. Maybe you could add some epsilon earlier on, instead.



Accusations against 鲁炜, till recently Director of the Cyberspace Administration of China




We didn't start the fire
It was always burning since the world's been turning

We didn't start the fire
No we didn't light it, but we tried to fight it

A crude conversion, without proper word spacing:

yǐ quán móu sè, yáng fèng yīn wéi, móu qǔ lì yì, shòu huì fàn zuì,
dà gǎo tè quán, háo wú dǎng xìng, gōng qì sī yòng;

sì yì wàng wéi, mù wú guī jǔ, yǐ quán móu sī, wéi fǎn jì lǜ,
qī piàn zhōng yāng, gè gè jiē wú, qíng jié yán zhòng.

wàng yì zhōng yāng, bù zé shǒu duàn, yě xīn péng zhàng, xiàng xiàng wéi fǎn,
pǐn xíng è liè, zhuān héng bá hù, xìng zhì è liè, shōu shòu cái wù;

lā bāng jié pài, nì míng wū gào, shōu qián liǎn cái, zuò fēng cū bào,
gān rǎo xún shì, háo wú lián chǐ, duì dǎng bù zhōng, lǐ xiǎng quē shī.



Hard-won knowledge for printing from Ubuntu
sudo /etc/init.d/cups restart

Otherwise you won't be able to do anything.


Hard-won knowledge for convenient SSH
So I can't ssh straight into my department anymore. I have to go through a jump server the college operates, so they can 2-factor authenticate me. Annnd then I have to go through the department's gateway, since the machines inside don't have domain names.

Here's how I've made that less a pain in the butt. It requires ~/.ssh/config, ssh-agent (called in ~/.profile), ssh-add, one invocation of ssh-keygen, and an invocation or two of ssh-copy-id.

1. Set up the config file. Give each host a handy short name, specify the hostname if it has one, give it the User parameter if your username there is different. This way you can just say "ssh shortname" And give a ProxyCommand parameter for anything that has to be proxied through another machine: ProxyCommand ssh -A -W %h:%p

2. To your .profile add: eval `ssh-agent -s` # or -c if you run a C shell or similar. Also, to start the agent for your current login, issue that very eval command. The agent remembers identities you have and uses them for authentication.

3. If you haven't already (check .ssh/ for a .pub file containing your public key), make a keypair with ssh-keygen. This keypair will prove to the hosts that you're you.

4. ssh-add in order to give that identity to the agent. It will hold it for some configurable length of time. You'll need to type the passphrase to unlock the identity if that lifetime expires.

5. ssh-copy-id # for each host you want passwordless login to. The -A switch in the ProxyCommand tells the proxy to forward authentication requests from said host back towards you.

So now I type "ssh my-lab-group-server" with great satisfaction. The config file takes care of imposing the right username and proxying my way in, the authentication forwarding and my ssh-agent prevent password prompts on department machines, it almost entirely Just Works.

The one thing I'm missing is that I can't do passwordless login to the original college jump server. They only allow that if your second factor is Duo Push on a smartphone, which I don't do. So I do have to type a password there, for now. But once I'm past that, all the department machines know and respect my keypair, so as long as the agent has it loaded I face no further rigmarole.

OK, the one other thing is I have to do this from within my crouton chroot, since the ChromeOS CLI facilities are ... let's say deficient. But I'm starting to just have crouton open all the time anyway, for the same reason.


Hard-won R knowledge for Inter-Rater Reliability / Inter-Annotator Agreement
I have each item on a row, and then I merge my raters with one column per rater, like any sensible person would do, but of course R's irr library doesn't like that. Eyeroll.

Importing the file as a character matrix: test_matrix <- as.matrix(read.table("Documents/IRR-trial-run.txt", sep="\t", colClasses="character", na.strings = "", row.names="V1")) IRR package: > library(irr)
Error: ...
# employ profanity
> install.packages('irr')
# answer some questions
> library(irr)

> kripp.alpha(t(test_matrix), method="nominal")
# Note the t() that transposes the matrix. Our ever-logical friend R expects each rater to have their own row, and each item to have its own column. Because of course it does.


Learn to Open Master Locks.
Master brand combination locks are widespread and not great. Here's a program to practice the mental math involved in cracking them. Use this information for good, not evil.


use strict; use warnings;

# generate a Master-type combination
my $first = int rand 40;
my $third = ($first + 4 * int rand 10) % 40; # congruent to first mod 4
my $second = ($third + 2 + 4 * int (1 + rand 8)) % 40; # congruent to first+2 mod 4, not equal to third +- 2

# front disc is serrated so third number can't be found just by tensing shackle and turning dial
my $true_gap = $third % 10;
my $false_gap = ($third + 1 + int rand 9); # non-identical digit

# give discoverable clues
print "\n", 
  "back disc rubs at ", ($first + 34) % 40, ".5\n",
  "front disc catches at ", join(' and ', sort {$a <=> $b} ($true_gap, $false_gap)), 

# candidate thirds
my %thirds = map {$_ => 1} 
  grep {
    ($_ % 10 == $true_gap or $_ % 10 == $false_gap) and 
    $_ % 4 == $first % 4
unless (exists $thirds{$third}) {print "     this program is broken     \n\n"; exit 1}

# user silently calculates first number
# user calculates candidate thirds

while (scalar keys %thirds) {
  print "enter candidate for third number: ";
  my $cand = 0 + <STDIN>;
  if ($cand % 10 != $true_gap and $cand % 10 != $false_gap) {
    print "not congruent to a gap\n"; next
  if ($cand % 4 != $first % 4) {
    print "not congruent to first number\n"; next
  delete $thirds{$cand};
  print "accepted\n";

# user tries each candidate on the lock, under tension
print "\nthat's all of them. $third has the most play\n\n";

# user must try up to eight possible second numbers
print "you have eight guesses. enter each combo with whitespace between numbers";
for (1..8) {
  print "$_. ";
  $_ = <STDIN>;
  /(\d\d?)\s+(\d\d?)\s+(\d\d?)/ or next; # perl-haters, there's your line noise
  if ($1 == $first and $2 == $second and $3 == $third) {
    print "\nsuccess!\n\n";
    exit 0

# how to fail
print "\nsorry, you're out of time. the combination was $first $second $third.\n\n";



Installing Factor
In Ubuntu Trusty Tahr, these are the machinations whereby I got the Factor X11 listener (better known as a REPL) running:

Download, gunzip, and untar the binary package. tar -x by itself apparently just sits around, you have to tell it tar -xf because telling it that you want to extract files does not carry the implication that you want to extract them from somewhere.

At this point ./factor chokes to death:

./factor: error while loading shared libraries: libgtkglext-x11-1.9.so.0: cannot open shared opject file: No such file or directory

To fix that, [if you have not updated your apt-get databases in a while, start with sudo apt-get update, and then] sudo apt-get install libgtkglext1

Now you can have a different problem. Now the error is

Cannot resolve C library function
Library: DLL" /usr/lib/libgtk-x11-2.0.so.0"
Symbol: gtk_init
DlError: none
See http://concatenative.org/wiki/view/Factor/Requirements
(U) Quotation: [ c-to-factor -> ]
    Word: c-to-factor
(U) Quotation: [ [ catchstack* push ] dip call -> catchstack* pop* ]
(O) Word: command-line-startup
(O) Method: M\ gtk-ui-backend (with-ui)
(U) Quotation: [
       OBJ-CURRENT-THREAD special-object error-thread set-global
       current-continuation -> error-continuation set-global
       [original-error set-global ] [ rethrow ] bi

As you can see from the stack trace, we're now failing during the execution of actual Factor code. Exciting!

The Internet suggests that this might be fixed by installing gtkglext-devel (a package which turns out not to exist).

The Internet then suggests that it can be fixed from the command-line listener. Here IN: scratchpad represents the listener's prompt.

./factor -run=listener
IN: scratchpad "gtk.ffi" reload
IN: scratchpad save

Now, by the way, you will probably want to exit the listener, which is done by executing the command

IN: scratchpad return

Now your attempts to run ./factor will barf out a different error, in that the symbol that cannot be resolved is gtk_gl_init instead of gtk_init. Everything else is the same.

At this point I thought it a good idea to try and visit the Requirements page that was mentioned earlier. Cleverly, the main Factor website and the "Getting started" page do not see fit to link it. But now the secret is out. The requirements page lists a whole bunch of prerequisites:

sudo apt-get --yes install libc6-dev libpango1.0-dev libx11-dev xorg-dev libgtk2.0-dev gtk2-engines-pixbuf libgtkglext1-dev wget git git-doc rlwrap gcc g++ make

Also a helpful "Note that if you are using a proprietary OpenGL driver, you should probably leave out the libgl1-mesa-dev package in the list." A moment's rereading will reveal that there is no libgl1-mesa-dev package in said list. You should probably also leave out the all-the-other-packages-real-or-imaginary-that-are-not-in-the-list package in the list.
At any rate, once I'd installed those, or at any rate the ones that I don't already have, calling factor without an argument finally did bring up the X11 listener. 
For my next adventure, I'll find out whether that listener genuinely works, or if it's just good enough to load. But for today I've converted a reasonable amount of enthusiasm for Factor into an unreasonable amount of eye-rolling, so I'll knock off here.



Installing Lingua::Wordnet
Eight years later: My views on same-sex marriage, and on judicial remedies, have changed.

But anyway, I'm here to talk about the Perl module Lingua::Wordnet. I am installing this in a local::lib and a good thing too, because my sysadmin would not put up with this much nonsense.

After unpacking it, you have to go into the scripts directory and run convert_db.pl in order to turn the WordNet data files into Berkeley DB files. If you intend to put your DB somewhere other than the default, which if you're not the sysadmin you do, you have to create that dir first.

Now you can make.

Next, Wordnet.pm hard-codes the location of the DB, so you'll have to change that.

Now you can make test. And fail the tests.

Next, the tests hard-code a DB offset for a synset, which is correct as of WordNet 2.0, 12 years ago, but is not correct for, say, WordNet 3.1 (in which it's 00472688%n OBVIOUSLY). So you'll want to stick a line into the early reaches of t/01.t that will look up the synset "baseball","n",1 and die printing its offset. Then you'll have to edit that into 01.t and 02.t.

Next, in 01.t, test 5, the familiarity of 'boy','n' is now 3, and 02.t seems to be crashing during test 6. Maybe.

I'm now searching through 02.t for what goes wrong by the sophisticated process of dying at various points in it. Note: Don't use Ingy's XXX function on any large objects like synsets, wordnet handles, etc. It's awfully slow to die.

Annnd, the statement producing spectacularly unhelpful error messages was $synset2 = $wn->lookup_synset('human','n',2);

Why is that? Because 'human/n' now stops at one synset. Crap on a cracker.

AHHHH! All the tests pass now! That only took 90 minutes of groveling. Now we can make test. Let's try make install and see what new horrors await.

... no new horrors. make clean for good measure. Annnd the module runs!

Powered by Blogger


(K) 2002-present. All rights reversed, except as noted.

Hard-won technical knowledge, old rants, and broken links from 10 years ago. I should not have to explain this in the 21st century, but no, I do not actually believe the world is flat.