With this blog being hosted at and titled after my ham callsign, one might
expect there'd be at least a little radio-related content by this point.
I promise there's some coming, but for a stopgap enjoy this cover shot of a
pamphlet I found buried in the depths of a local antique mall:
I sure do love the literalness with which technical publications are titled.
I was too overwhelmed with the sensory overload that is shopping at an antique
mall to note down any interesting details, such as the date of publication.
It didn't have a price listed and it seemed like too much trouble to ask, which
is probably good since I really don't need such a volume in my possession.
Still, if there's one interesting takeaway, it's that Sylvania used to make
scopes?
I touched briefly on
Stevenson screens
in my
post on hygrometry,
mostly with regard to how cool it is that their namesake was the father of the
famed author.
I'd never built one myself, and never planned on doing until I get around to
building a full-on weatherstation like I will, uh, one of these days.
Then I realized: my girlfriend has a cheap wireless indoor/outdoor thermometer,
and the remote sensor can be a bit, uh, touchy:
I promise it was not 122degF when this picture was taken (low 80s, if even).
Sometimes I think "keep out of direct sunlight" warnings aren't taken
seriously enough, especially at our altitude.
(With apologies to any readers unaccustomed to Farenheit units, this
reading is about 50degC, with ambient temperature below 30).
Things like this are why we have 3D printers, so I decided to see what might be
publicly available as far as enclosure designs and run one off real quick.
It turns out, Thingiverse has
many options;
I picked
this one
somewhat arbitrarily.
This was a pretty cool model and brought me a little farther into the mindset
of 3D printing than I've been in the past.
I know there are some pretty radical things you can do once you get there
—
printing individual screw threads or a ball in a cage or an entire planetary
gearset in situ
—
but so far I've always treated it as just another traditional
process, proceding from a drawing and using exact dimensions with known
tolerances etc.
This was a different experience:
since I didn't really have any specific dimensions to match, I just scaled it
with the dumb slider in my slicing software and ran test prints until it was
about the right size to accomodate the sensor.
Also, the height of the whole assembly isn't fixed, instead you can print as
many of the annular section as you want to make it as tall as you want.
I did that until it looked reasonable, drove a self-drilling plastic screw
into the bulkhead to mount the sensor, found some all-thread and acorn nuts to
tie it all together and called it good.
If what you built looks janky, just bolt it to something that looks even
jankier that you didn't build.
50/50 which goes first, the enclosure or the fence.
At the end of the day, it was pretty fun building something so
fast-and-(literally-) loose, the sensor works much better, and I think it looks
altogether quite professional hanging there on the back fence.
When I was working on getting this site hosted, I knew I wanted some basic
metrics reporting.
Just basic view counts and referrers, nothing so overbuilt or privacy-invasive
as Google Analytics.
Server logs would suffice but my hosting doesn't provide them, and I've been
out of the web game for long enough I started asking around my friends for
suggestions.
Among the responses I received was the following:
LOL I remember those hit counters from the 2000s
You are visitor NUMBER 219
Just do one of those
It would legit be so cool
Another friend, somewhat more helpfully, suggested
GoatCounter.
This ended up being a pretty perfect fit for my needs and what I ended up
going with, but my mind had already worked through how we might go about
building a geocities-style hit counter in 2020.
Plus, it would legit be so cool.
So, let's do it!
Basic requirements
For those who aren't clear what I'm talking about, what we're going for here is
something like this:
An old-school hitcounter, mined from the bowels of archive.org and used
without proper attribution like I'm the British Museum or something.
I'm not sure how these actually worked, I'd imagine just a cgi script that used
ImageMagick to glue the digit assets together into a final image,
although if you had cgi rendering yourself you could probably just
stuff the img tags for the correct individual digits and do it that way.
The former technique sounds better for our present purposes, for reasons that
should become apparent.
It's 2020, so let's assume the visiting browser can display SVG.
Let's try and write an endpoint that will keep an internal count, increment it
on each GET request, and return that as an SVG that shows the current number.
We can then just put that into an img tag and it should just work with
no AJAXy complications or client-side anything.
Note, though, we don't currently have any server-side anything either
(this site, for instance, being statically-hosted).
To make anything work at all, then, let's put our new endpoint up on
Google Cloud Functions.
This is google's version of lambda/serverless, and should allow us to (1) write
a function to service the GET request, (2) communicate with one of the various
datastores that google provides to maintain the counter state, and (3) render
the SVG code for return as the response payload.
Let's take these tasks in 3, 1, 2 order:
Implementation: the display
For this, we want something that takes an arbitrary integer and returns an
SVG document that draws it like we want.
Let's use the
drawSvg
module for the SVG manipulation.
First nail the basic geometry by assuming (very optimistically) six digits to
display, which we'll zero-pad from the left.
Make each digit 40 pixels wide by 60 pixels tall.
importdrawSvgasdrawN_DIGITS=6DIGIT_WIDTH=40DIGIT_HEIGHT=60defget_counter(n):"""Given integer n, returns a drawSVG.Drawing representing n"""d=draw.Drawing(width=N_DIGITS*DIGIT_WIDTH,height=DIGIT_HEIGHT)returnd
This will give us an SVG with the correct geometry, although there's nothing in
it yet.
We'll want some digits in there, which we'll presumably want to draw
individually, so let's write a helper that breaks those out:
defget_digits(n):"""return list of single-character strings, left-to-right the digits of n, zero-padded to N_DIGITS. Raise AssertionError if n exceeds representable digits"""fmtstr=f'{{:0{N_DIGITS}d}}'digits=list(fmtstr.format(n))assertlen(digits)==N_DIGITS,f'Overflow trying to fit {n}' \
f'into {N_DIGITS} digits'returndigits
Since we want the result to pad out to N_DIGITS, and since this might
conceivably change, we double-bag it on the format string.
Given the default value above, fmtstr ends up being '{:06d}',
and thus get_digits(69) returns ['0', '0', '0', '0', '6', '9']
(Note: there are many, many other ways of accomplishing what we're doing,
some of them a lot more fun.
Quick reminder that "fun" is not necessarily an admirable objective when
developing software in a a pedagogical or team environment).
Although perhaps not in keeping with the spirit of the geocities inspiration,
we also aggresively trap for an argument that's not representable in the given
width (instead of silently overflowing, trying to paste ancillary characters
outside the bounds of the SVG draw area, throwing a weird internal exception,
or other unexpected behaviors).
Thus, get_digits(1234567) will raise AssertionError.
To be totally rigorous, we should also check to make sure the argument is an
integer and positive-definite.
In a professional environment, we'd probably (hopefully?) have unit tests to
ensure that exceptional cases like these are handled with a minimum of surprise.
Now that we have these digits, we can apply them as text elements to our
Drawing.
We add these to the d object within the scope of get_counter above
like so:
There's some munging going on to get the characters to align, the details of
which I'll spare you (protip: just monkey with it between some combination of
code tweaks and devtools with liberal use of the refresh button until it looks
right).
We will, however, bow in the general direction of cross-platform visual
consistency by specifying a font, and go ahead and make the numbers white, to
show up against the dark background.
Except, we don't have a dark background yet.
The following needs to be specified farther up in the procedure so as to end up
on a lower z-layer, but we can use an SVG gradient to get the awesome drum
counter visual effect:
Put all together, get_digits(31337) then returns an SVG document that looks
something like the following:
Not bad for 55 lines of (mostly whitespace) code with no static assets!
Implementation: the deployment
To catch up, we now have a thing that, given a number n, will provide a
fancy SVG graphic depicting that number n in the most awesome 90s-tastic
fashion possible.
It remains to host this someplace that will allow for retrieval of those
graphics over the public internet, and which will hopefully take care of
incrementing n once per such request, thereby making it a proper hit
counter.
First, let's test that our svg generation works in a deployed environment.
Define a function test_counter of one argument, the flask.Request
object representing the request, but which we'll then ignore.
Have this return the svg data from a static n:
We also need a requirements.txt telling GCF that our execution environment
needs drawSVG.
Getting that to actually work will require a bit of configuration in the Google
Cloud Console, and I'm going to consider that out-of-scope for this treatment.
For one, there are a million blog posts out there about "how to get
started with Google Cloud Functions" that go through all that in detail, for two
they're all also probably out-of-date because google seems to change the
minutiae of the configuration pages quite frequently.
Google's own documentation is kept current and also surprisingly good, and I'd
refer any readers actually playing the home game to that.
Anyway, (waves hands), given the configuration of my project, this endpoint
is now available at
https://us-central1-geocities-counter.cloudfunctions.net/test-counter.
Amazingly it just works, with the xmlns presumably saving us from having to futz
about with MIME types or anything.
Now, to make it tick...
Implementation: data persistence
The GCF execution environment is ephemeral, so obviously we can't just use a
global variable to retain the counter state, or anything else that's tied to
the interpreter lifecycle.
So, although it seems like overkill, we're going to need to set up an external
datastore to retain this state.
GCP is absolutely resplendent with options for doing this: we could use
google's Bigtable or Spanner, a hosted instance of MongoDB or Postgres,
even a flat file in Google Cloud Store.
For this walk-through I'm going to use
firestore,
which is a NoSQL/document database that google took over with their acquisition
of Firebase and that's easy to get started with for projects like this.
As above, I'm going to skip the details of actually setting this up on the
Cloud Console side.
Suffice to say we've set up firebase for the project and defined a collection
called counter.
Now, we add a second function:
fromgoogle.cloudimportfirestoredefget_counter(request):"""GCF entrypoint: retrieves counter state from firestore, initializing if necessary. Increments this and persists to firestore, returning svg payload of counter displaying new count"""db=firestore.Client()doc_ref=db.collection(u'counter').document(u'count')doc=doc_ref.get()old_n=doc.get(u'n')ifdoc.existselse0n=old_n+1doc_ref.set({u'n':n})returndraw_counter(n).asSvg()
We need the firestore module from the google.cloud package for anything
to work, and this must be added to requirements.txt also.
Despite being a google product, the Cloud Functions execution environment
doesn't inject every possible dependency into the runtime, which we may assume
is a Good Thing.
When we instantiate a database client via firestore.Client(), it
does know, somewhat magically, that we want to connect to the project's
firestore, and it handles this connection, including authentication.
This is actually totally awesome since anyone who's done work like this
knows getting things to talk like that usually takes at least an hour, along
with a large repertoire of curse words to get working properly.
Even though the value we're trying to store is scalar, firebase makes us go
through a couple layers of abstraction to get there (being, of course, designed
to operate on much larger collections of data, the value of which we'll see in
the conclusion).
First, we must identify a collection (here called counter), which must be
created either through the Cloud Console or via calls to an API we won't treat
here.
The unit over which these collect is called a document, and these are
identified by unique keys.
We use the magic key count to identify our single document of concern.
The documents are (potentially heterogeneous) associative arrays, which are not
yet retrieved at this point.
This allows us to check for existence and apply default data if necessary,
allowing document lifecycle to be managed at the application level even after
relegating that of the collection to the infrastructure.
Finally, our scalar value of interest lives on a field of the document we call
n, which we then retrieve if the document exists and default to 0 otherwise.
We then increment this value, overwrite the document with the new value of
n, and pass it to draw_counter to generate our response.
Although it worked above when directly loaded, it turns out that browsers don't
seem to render svg in an img tag without either a .svg file extension (which
GCF doesn't allow) or the correct mimetype set, so we force that on the
response.
We then should be able to use this as the src value of an img tag,
like so:
Sit there and jam on the refresh button for a minute to prove to yourself it
works.
Conclusion
So that was fun, we've accomplished our goal of implementing a
server-side-rendered graphical hit counter I feel is very aligned with the
spirit of those we saw on geocities and other sites in the early days of the
public web (even if "server" has become a much more nebulous concept since
then).
Project files may be found at
https://github.com/drewhutchison/geocities-counter.
From here, there's a few places we could obviously go:
If we don't trust our clients to render SVG, we could use drawSVG's
.rasterize() method to return it as a PNG or whatever.
Maybe even predicate this on the contents of the request's Accept header.
A lot of hit counters back then used a different visual presentation:
7-segment displays, plaintext, or whatever.
My feeling is, if we're going old-school skeumorphic, go old-school
skeumorphic and stick with the drum counter, but since this is encapsulated
in the draw_counter method, we can really do whatever we want.
For instance, a bit of signed random added to the y argument of the
draw.Text constructor would give us a bit of jitter to the drums for a
more authentic 90s feel.
There's a classic race condition, in that two simultaneous requests
might retrieve the same n and both overwrite with their n+1, resulting
in a count being "lost".
Obviously this would be unacceptable in certain applications, and there are
well-known ways of preventing this, but I think for a simple hitcounter we're
OK.
This is really the dumbest thing possible and, except for the concern just
noted, will advance the count on each discrete GET request.
Most things like this are minimally interested in unique visitors.
Since we have access to the entire Request object, we could tamp this
down by IP, set a browser-side cookie, or employ some more-sophisticated type
of client fingerprinting to account for non-unique views (this need not be
invasive, in fact, GoatCounter employs a rather ingenious mechanism to track
unique visitors in a GDPR-compliant way. See https://www.goatcounter.com/gdpr
for details).
The latter would entail a modification of our use of the datastore: instead of
just incrementing a scalar n we would keep a set of identifiers (the IP,
some uniquely-identifying hash, or whatever) and return the cardinality of this
set.
The use of a document-store database like firestore makes this a very easy
change.
At that point, we could also do away with the magic key n entirely and
instead use the URL as determined from the request, and we're well
on our way to a full-featured analytics system, despite not changing the
public-facing API (of a GET to the function endpoint) at all.
But, that would have the effect of reducing the total count, and I think I get
more internet points the higher that thing goes.
Did you refesh the page to prove to yourself it's working yet?
A little over a year ago I decided to start carrying a multitool and,
not knowing anything about the product field, picked up a
Leatherman Wave+.
I've been more-or-less completely satisfied with it:
the scissors and wirecutters tend to runout a bit, but they're nonetheless
quite handy and the screwdriver is ingenious and by this point I can hardly
conceive of going back to life without it on my hip.
Now, this blog isn't, nor is meant to become a prepper/EDC lifestyle
accessory review site: there are plenty of those, they do a great job and
even go on to tell you what kind of hunter camo is in fashion this season to
wear at your wedding and stuff.
I digress.
I had an unpleasant experience attempting to service the above-mentioned
tool, and am calling that out as part of what I find to be a disturbing trend.
Last week I took my Wave+ fishing, and it ended up spending an hour or so
rolling around in bilgewater after falling out of my lap after I used it to
unset a hook and, well, you know how these things go.
This isn't the most respectful way to treat a tool, for sure, but it hardly
constitutes severe abuse near what you'd expect to damage a field tool like
this.
When I got back to shore I dried and oiled it but noticed it had picked up some
sediment internally that made the action a little gritty.
No problem, I'll just take it apart and clean it, as I've done with my
pocketknife any number of times.
Sound good?
Sounds good.
Except...
What the hell is THAT doing there?
There's an old joke about the Soviet economy: "they pretend to pay us and we
pretend to work."
I've always thought similarly about security fasteners: the manufacturer
pretends they're sufficient to deter any attempt at meddling by the end-user,
while the end-user pretends not to attempt any field service since the
requisite tools are obviously impossible to obtain.
In point of fact, bit sets for these fasteners were one of the first things I
remember e-commerce being AWESOME at providing.
I ordered a set in about 2003
(from an online vendor that wasn't Amazon... how different things were).1
Now, if ownership of a
security torx
bit set is the cost of entry to
attempt repair on a modern Leatherman tool, I'm basically OK with that.
Really I am.
It sets a baseline of earnestness and competence:
you've marketed the thing as a fine tool for the distinguishing craftsperson,
and you don't want your warranty department getting bogged down when JimBob
McHarborFreight ends up bending his the wrong way with help of a rusty
screwdriver and a pipe wrench.
Makes perfect sense.
And, in any case, I've got the right driver, right?
Except...
Double-what-the-hell-is-THAT doing there?
Yeah.
I have one.
Because anyone only ever needs one.
This is like how anyone only ever needs one can opener:
your can-opening needs, such as you ever encounter them, are perfectly met by
owning exactly one of the tool for the job, and no one is dumb enough to try
and design a can that needs two can openers applied simultaneously to work.
Yet there on the other side of the tool, that fastener mates another one with
the same drive type, necessetating a second identical driver.
This is literally the first time in my life I've ever had need of turning
more than one security fastener at the same time.
And it's all because some smartass engineer decided something like:
these fasteners just look cooler
(more cynically) we can drive repair revenue and product attrition through
by imposing arbitrary hurdles to owner maintenance
(most cynically) we can use this to drive2
sales of interchangeable bits
It turns out, (3) doesn't even make sense, since they don't even sell security
bits.
Way to hold the line there, leatherman, and keep those bits out the hands
of dangerous criminals.
You know, the types who might try to replace the battery in their own iPod or
turn off the seatbelt warning chime.
I honestly can't relate to the mindset of someone who, owning a tool company,
would intentionally design and build tools such that they can't be used to
work on themselves.
I suppose there's a possibility the reasoning aligned with another option:
we really don't trust our customers to undertake repair of their own tools
Looking at third-party repair instructions,3
there's some medium-complicated stuff in the stack: eccentric bushings and
friction washers, but nothing all that gnarly.
It can't any more complicated the spring-assist on my pocketknife, and that's
something I've torn down to clean enough times to confidently do it
blindfolded by this point.4
Again, I'm basically satisfied with the tool in every other way, but this kind
of overt hostility toward what should be their key customer base is the sort
of thing that might have me looking elsewhere when it comes time to replace it.
Remember I'm not a review site, and I wouldn't recommend against buying this
tool for this reason alone.
Just, if you plan on undertaking a major service operation — like cleaning
some grit out of the mechanism — without sending it in for factory service,
remember to factor in the cost of an extra set of security bits to the TCO.
Then you get to be the only person on your block with both a shiny new
Leatherman tool and an otherwise completely redundant set of same.
Notes
Around the same time, I worked in a physics lab with a Polish woman who
had a set that her boyfriend had machined himself.
If that isn't the most Eastern-European thing ever, I don't know what is, but
the point remains: these bit sets are not now, nor have they been remotely
difficult to obtain since about the turn of the century, at latest. ↩
No pun intended.
I considered changing it to "spur," but, same problem. ↩
Because third-party instructions are all that seem to exist.
I kind of doubt the existance of any kind of real service manual, maybe because
the
"User Guide"
is just a two-page product brochure covering their entire multitool line in
twelve languages.
Rest assured, though, their main website clocks in at 10MB over 168 requests.
Leatherman: we may not stand behind our products, but we'll glut your
connection with 10MB of FMV lifestyle porn in the attempt to sell you a new one.
Think of it as multitool-as-a-service.
↩
OK, maybe the spring-assist is a little easier because it uses regular Torx
fasteners because Kershaw are decent human beings.
To argue that's any real advantage begs the question. ↩
Every year about this time the weather starts getting interesting,
and every year about this time I kick myself for having forgotten that the
NWS SKYWARN spotter training classes are
only offered in March and April.
Luckily this year I wasn't doing much in April and our local NWS office did a
FABULOUS job of presenting the training courses online, so I finally got my
spotter ID.
It's causing me to pay more attention to the weather and to meteorology as a
science than I have since probably high school.
Aside from some intense thunderstorms this week (including a tornado warning
for cloud rotation observed within city limits, which is VERY uncommon),
it's also been uncharacteristically humid.
Humidity is something we don't often pay much attention to around here, on
account of its often never exceeding 30%.
But I started reading up on the subject and learned a couple interesting things.
An instrument used to measure humidity is called a hygrometer, and they can
be made to work by any of various phenomena.
Mechanical dial-type gauges, such as may be found with a thermometer and
barometer on a wall-mounted weather station, work by measuring the distension
of a material or structure that expands and contracts in response to
atmospheric humidity; human hair is one such material.
The dielectric constant of air changes with humidity, so an electronic gauge
can be made to work by measuring the time constant of a circuit built around an
air-gap capacitor exposed to atmosphere.
Alternatively, solid-state sensors exist which utilize a material whose
resistance or other electrical properties change after absorbing moisture from
the air.
These are found in some consumer devices, as well as in the form of discrete
components which can be purchased and integrated with hobbyist or industrial
systems.
From left: Home weather station with dial-type hygrometer, barometer, and
thermometer (via Wikimedia Commons, photo by Friedrich Haag);
Low-cost digital thermometer and hygrometer;
Hobbyist capacitative-type discrete humidity sensor from Sparkfun
A different, arguably more direct device works on the principle that
evaporation occurs in inverse relationship to air humidity.
Thus, by measuring the temperature differential between the ambient air and an
thermometer evaporatively cooled in presence of same, the air humidity can be
determined.
Such a device is specially called a psychrometer.
These have two thermometers, one "dry bulb" which measures ambient air and one
"wet bulb" which is moistened and reads cooler because of evaporation.
Though I never built one, I remember seeing instructions in many childhood
science books for how to do so.
These were swung around over one's head to create enough airflow to
sufficiently cool the wet bulb; precision instruments use a calibrated fan or
blower to provide a known amount of airflow.
The relationship between humidity and the two temperature readings depends also
on barometric pressure, and, if there's an analytic formula describing this
relationship, I've never seen it.
Instead all I've ever seen are tables, like the one shown below.
In fact, I was given advice by a Physics instructor once to the effect of
"you need a CRC manual from roughly every ten years or so, because they stop
putting certain information in there.
I was trying to find a wet-bulb hygrometer table the other day and I had to
go back YEARS."
Fortunately my 1967 edition has one (alongside other then-useful-now-WTF
data like a table of haversines and the physical properties of whale oil).
Table of humidities as determined by psychrometer reading, from the CRC
Handbook of Chemistry and Physics, 48th ed. (1967-68)
Sources of error for this instrument will be temperature effects from things
other than the ambient air: radiative heating from the sun or other
surroundings, or direct impingement of precipitation, for instance.
You've probably seen the louvred enclosures at an airport (or, as my brother
pointed out, at the end of the coincidentally-named movie Heat) that are used
to mitigate these effects while allowing air more-or-less to freely circulate.
I learned from the
wikipedia article
that these are called Stevenson screens and that their namesake inventor was
the father of Robert Lewis of the name, author of Treasure Island (!)
A Stevenson screen
To conclude with one more interesting factoid from the wikipedia article:
One of the most precise types of wet-dry bulb psychrometer was invented in
the late 19th century by Adolph Richard Aßmann (1845–1918);
in English-language references the device is usually spelled
"Assmann psychrometer."
My girlfriend teaches at a local college which has what they call an
"innovation and creativity center."
This is like a hackerspace for college students: they have a small laser
cutter, a bank of FDM machines, the standard stuff.
Its purpose is to give students access to resources for product design, rapid
fabrication, and other resources that students might employ throughout their
studies.
When COVID hit and they sent most of the students home, a small group of
faculty and remaining student workers started turning that equipment to
produce PPE for local hospitals and other communities in need.
So now they're making face shields, with a throughput of a few hundred units
a week.
They're making a couple different models from the many that exist now (the
fire department has stated a preference for one model, the hospitals like a
different one) but which are of the same general construction.
This consists of an FDM1
or resin-cast frame, which holds an elastic piece in back for securement and
a piece of transparent PET sheet that comprises the shield.
The latter piece is laser-cut both for general shape and for holes to mate the
mounting studs projecting from the frame.
Assembled facemasks.
Note the mounting of the shield material to the frame.
One problem they were having is that the PET sheet they've sourced comes
on giant rolls, and the material needs to be rough-cut down to fit on the
16x12" bed of the laser cutter.
The rolls are 48" wide, so this is easily enough cut down to either dimension,
but the difficulty comes in making a transverse cut across 48" of material
that's both reasonably square and also close to the 10" dimension required by
the design.
Some slop is forgivable, since each shield is cut entirely from the interior
of a piece and none of the rough cuts forms a finished edge.
But it was still proving awkward and cumbersome to measure and square this by
hand while managing the not-insignificant bulk of the roll itself.
This had become a bottleneck in their production process, so I decided
to build a fixture to help.
Design
Design requirements were pretty straightforward,
the fixture needed to provide three things:
Some means of retaining the spool and allowing it to unreel in slightly more
elegant fashion than just flopping around on the work surface.
If this can help prevent its getting scratched, so much the better.
Some form of stop, square to the axial dimension of the reel and against
which the working end of the material can be abutted and made fast.
Some sort of guide, rigidly fixed at the desired distance of ten inches
from the stop, along which a cutting tool may be worked.
While this allowed a lot of flexibility in implementation, a challenge was the
timing.
This was late March, when Amazon was still quoting lead times of 4-6
weeks on "nonessential" items, hardware and home improvement stores were
basically closed and I myself was trying not to leave the house.
Here's what I made do with parts (mostly) on hand:
Assembly
The finished fixture
The base is cut from some 3/4" subfloor plywood I had lying around.
This was plenty rigid but a little more warped than was reasonable for the
application, so I counterbored from the top and tied into a couple
lengths of steel strut-channel, which got it straightened enough.
With the limited materials I had to work with, this construction is going to
be a bit rough.
The reel is stood-off on plywood that the innovation center was kind enough to
laser for me, sandwiching some scrap 1x1 for rigidity and tie-in to the base.
This plywood is slotted to accommodate a length2
of 3/8" all-thread, on which the ID of the spool sits.
The cutting guide is formed by the T-slot aluminum frame you see swung up
toward the image foreground.
This is corner-braced internally but also tied to more plates of laser-cut
plywood you see up top.
Edge-to-edge the frame is 10" wide,3
and serves to measure this dimension in the direction toward the spool from
the plywood seen screwed into the base, to which the hinges are affixed4
and which serves as the material stop.
When the frame swings down, it then defines a transverse line 10" from the
stop.
It can be toggle-clamped to hold the material in place, allowing the
operator both hands free to manage the cutting tool.
Lessons learned
Altogether this was a very edifying experience, for a couple of different
reasons.
From a systems perspective, this was a great object lesson in limiting
features to scope of project.
The engineer in me looks at requirement (1) above and
immediately starts picturing endcaps that mate the ID of the spool and ride
on sleeve bearings about the axial shaft such that the spool is balanced
around its CoG and spins freely and etc. etc.
That part of me is absolutely horrified at the thought of the spool just
rough-riding over a threaded rod like a roll of toilet paper5.
Yet, it turns out, that works just fine.
Any attempt to build a more elaborate mechinism would have taken longer to
design and fabricate, imperiled ourselves of a re-roll if something didn't fit
or otherwise work, and been more likely to fail "in the field."
Not the most elegant way to hold a spool, but it works.
Similarly, I spent a lot of time thinking about requirement (3) and whether it
would be easiest to build a hot-wire cutter versus a straight, hook, or rotary
blade, what type of guide would best support same, what kind of clearance or
cut-out might be required to accomodate the tool in the bed, etc. etc. before
shelving all of that and just building a straight-edge.
I figured that, if a captive tool were that important, I'd hear about it.
In point of fact, the operators have made do with a manual knife just fine;
the straight-edge was all that was required.
There's a lot of talk in engineering circles about the perils of
overengineering,
YAGNI,
project scope, etc.
For all that talk, actually being able to define and hit targets is still
largely a matter of good judgement and experience and something even seasoned
engineers often fail at.
Being able to take something from concept to delivery and see that it
succesfully meets a need is a rare pleasure and one that keeps me in these
fields.
From a personal perspective, it was also very gratifying to be able to execute
with the limited parts and supplies I had on hand.
For years (at least when it comes to small parts like fasteners and toggle
clamps), my policy has been to buy 25-100% more than I need for the current
project "in case I need 'em for something else later."
This doesn't always feel like the right thing to do, what with the additional
clutter it brings and the alternative convenience of modern supply chains.
Most of my personal projects spend a long time in what we'll call the
"concept/pre-procurement phase."
Being forced by circumstance out of that and directly into execution with just
the stock I'd accumulated "in case I need it" was, again, a very gratifying
and singular experience.
Notes
I'm not going to engage the debate of that FDM isn't a
great process for this (that's obvious).
With some finishing work they do make great positives for the
poured-silicone resin-casting molds, and there's been talk of tooling up an
injection molding machine.
Plus, when we've still got nurses wearing trashbags, it seems like any number
of masks coming off the FDM line are better than letting it sit idle.
↩
actually two lengths joined by a coupling nut in the middle.
Like I said, this construction is rough↩
As close as I could get it with a friend's cut-off bandsaw.
My friend has a mill, too, but the extra accuracy didn't seem worth it given
the possibility of deflection.
In any case, it's within a few hundredths, plenty accurate for the application.
↩
The hinge attachments are slotted to allow for truing the frame against the
stops, as well as translating the frame vercially to accommodate different
material thickness or protective underlays.
↩
with apologies for possibly overspending on the COVID zeitgeist
↩
This gem
of a comment hit Hackaday today, on an article about "smart" speakers:
[i]n the millions of Americans out there who own these items lie a small
percentage of above average aptitude for electronics.
Excercise for the reader: assuming there are 20 million Americans who own a
smart speaker, how many would you expect to be of above average aptitude for
electronics?
Bonus question: how many are above average at math?
Over the holidays I undertook to revisit my knowledge of linear algebra.
I'd been talking with a friend, a college professor,
and we discovered that neither of us felt we really understood the subject.
We'd both studied it but we both
felt that pure math majors had attained some grasp of the subject that we
somehow missed while pursuing our more applied tracks.
We decided to grab some MIT OpenCourseWare and see what we could do about
rectifying this state of affairs.
We ended up with Gilbert Strang's Introduction to Linear Algebra.
Likely we could have done with a more advanced text, but
I personally felt my knowledge of the subject suffered in part from jumping
too quickly into the advanced matter without having paid my dues of late
nights solving factorizations and row reductions by hand.
Correspondingly, I commited myself to working every exercise in the
book from start to finish, skipping only those which were poorly-posed or
obviously trivial.
This has led to slow progress, but at least I won't be left with a feeling that
I'd left any portion of the subject untouched!
As is characteristic of MIT texts, Strang excels at posing review
problems that presage later material.
This is among the reasons that working the problems from start to finish
can be so fruitful.
Indeed, after a month spent pounding rote matrix arithmetic and
coming to an appreciation of the subject in the most wholesome
Weberian way possible,
I got as far as the following exercise
(Strang 2.5 exercise 21):
There are sixteen 2 by 2 matricies whose entries are 1's and 0's.
How many of them are invertible?
Now that sounds like an interesting problem!
The preceding chapter gives us a good idea what the matrix inverse means in
itself and in relation to singularness of the matrix,
but we haven't encountered any higher-level concepts that might answer this
question more simply or insightfully than attacking it by brute force.
There are probably things discussed in later chapers --
spans and nullities or something like that, concepts specific to the subject
and of which we're therefore admittedly ignorant
-- in light of which this question becomes trivial.
Being in ignorance of those for now, let's apply brute force methods and some
basic knowledge of algebraic structures to see what insight obtains.
Brute-forcing the solution
To begin with, the exercise states on faith that our problem space
comprises 16 matrices.
Do we know that number is correct?
Let's write our matricies in the form
\begin{equation*}
\begin{bmatrix} a & b \\ c & d \end{bmatrix}
\end{equation*}
with
\(a\),
\(b\),
\(c\),
and \(d\) each taking on one of the values \([0, 1]\).
This leads to \(2^4 = 16\) possiblities, the indicated number.
Again, we suspect there's some common property among the subset of these that
are invertible, but that begs the question.
We'll think through what these may be in a moment, but first let's try the
brute force approach.
16 isn't many cases to try, especially with a computer's help.
We know of a matrix of the above form that it's invertible iff
\(ad-bc \neq 0\).
It's easy enough to enumerate the possible combinations and count:
We see that indeed we're looking at 16 matricies and that, of these, 6 are
nonsingular.
This figure is all the exercise asked for, but we'd expect there's some
structure of interest regarding exactly which six of the sixteen have
this property.
We could display the results of the filter instead of counting them,
but let's see if we can reason through what they may be instead.
Reasoning the solution
The problem of finding which six matrices fit our criterion is that of
finding a particular subset of the 16 candidates.
Since each cadidate is either included or excluded from a given subset, there
are \(2^{16} = 65536\) subsets possible.1
With the blessing of foresight, we know we're only interested in those subsets
having cardinality 6.
There are \(_{16}C_{6} = 8008\) of these: we're looking for exactly one, and
don't currently know of any meaningful higher-level concepts to guide us.
This idea of cardinality is an interesting one, though.
Not having any better ideas, let's define \(N=a+b+c+d\),
the number of nonzero elements in each matrix, and see if that helps us break
down the problem.
There's one matrix with \(N=0\), which is clearly singular.
There's also one matrix for \(N=4\), which is also singular.
Any matrix with \(N=1\) is going to be singular, by virtue of necessarily
having a zero row.2
There are four such matrices.
The \(N=2\) case is the most interesting: two of these six matrices have a
zero row and are thus singular, likewise the two having a zero column.
The other two matrices (the \(2\times2\) identity \(I_2\) and the
\(2\times2\) exchange matrix \(J_2\)) are invertible.
Finally, there are four \(N=3\) matrices (distinguished by the position of
the single zero element), and each is nonsingular.
This brings our total to the desired 6.
We've thus answered the question left open in the previous section, and we know
which six matricies satisfy our criterion, and have some sense of their
underlying structure.
As before, we may be satisfied, but let's look deeper into what algebraic
structure(s) that might evidence from taking these together.
Investigating further
Under matrix multiplication, it's clear that any identity matrix \(I\)
forms the trivial group, and that \(\{I_2, J_2\} \cong \mathbf{Z_2}\).
Let us define \(B_a\) as the (\(N=3\)) matrix having \(a=0\)
(\(b=c=d=1\)), and \(B_b\), \(B_c\), and \(B_d\) analogously.
We see that any one of these along with \(J_2\) serves to generate the
others:
ghci has muddied that up a little for us with the Either and converting our
elements to Fractional, but it's clearly \([[-1,1],[1,0]]\), as we can
verify.
Since we know the other \(B_{\mu}\)'s
(\(\mu \in \{a, b, c, d\}\))
can be generated by left- and right-
multiplication by \(J_2\) and since \(J_2^\intercal=J_2\), we know we
can find the other inverses likewise.
This explains why we haven't encountered the inverses:
the elements of any member of our monoid
\(\mathbf{\{I_2, J_2, B_\mu\}}\)
are nonnegitive.
What would happen if we were to allow -1 as a value, in addition to 0 and 1?
This increases the problem space from 16 matrices to
\(3^4=81\), of which we know at least 10, but no more than 71 to be
invertible.
We can find the actual number by a slight modification of our first snippet:
That's more than I might have thought!
It's curious that \(16/27\ \ (\approx 0.593)\) of the \([-1,0,1]\)
matricies are invertible compared to only \(3/8\ \ (=0.375)\) of the
\([0,1]\)'s.
One must wonder what that function is like for other sets of integers.
It's about time to put a bow on this blog post, but there are some other
questions that seem to me obvious from this line of inquiry:
Of the 38 "new" invertible matrices, which, if any, are generated from our
initial set
\(\mathbf{\{I_2, J_2, B_\mu\, B_\mu^{-1}\}}\)?
What other characteristics lead to this determination?
Does this reasoning extend to other invertible matricies with integral
entries?
What about the \(3\times3\) case, the \(4\times4\) case, and on up?
It was cool running into \(\mathbf{Z_2}\), even if that's a somewhat
degenerate structure.
We'd expect the permutation matrices to form group structures in
higher-dimensioned matricies, but are there others?
Checking \(2^n\) matrices gets prohibitive very quickly, even if those
checks themselves were \(\mathcal{O}(1)\) (which, in point of fact,
they're very much not).
What deeper relationships exist to help us out?
Conclusion
Strang is very explicit in his preface that the book is to be used as I am
using it: that exercises are meant to fill a dual role of providing practice
while anticipating and foreshadowing concepts to come.
I find this characteristic of MIT OpenCourseWare: that the pedagogy
makes even lower-division undergraduate materials exciting and challenging,
even in fields in which I feel myself relatively accomplished.
This speaks (unsurprisingly) highly of the quality of the institution and the
faith they're able to place in their undergraduates.
So, on the one hand, it's cool to be able to jump into a simple-but-interesting
problem like this and run it to some conclusion.
On the other, I can't help feeling a bit foolish, like the answers to the
questions I'm inventing would be answered if I'd just pressed on with the next
chapter or two instead of digressing down this tack.
Back to the first hand, we have shown something in the way of how useful
the tools of Modern Algebra are at investigating different domains, even if
Linear Algebra is customarily taught first.
Notes
interestingly, this is the number of matrices we'd have if we were solving
the analogous problem for the \(4 \times 4\) case, one to which we may
return.
↩