It's easy to be complacent about curiosity. Our lives are filled with stress. We craft routines around that stress. Sometimes those routines turn life into an inescapable turnstile. It isn't easy to be curious when your life depends on keeping the wheel turning.
I spend a lot of my time solving problems with Python. Problem-solving demands creativity. Curiosity breeds creativity. The demand for creative software solutions is constant, but curiosity comes and goes.
When your curiosity wanes, don't resign yourself to the idea that some people are naturally more curious about things and that perhaps you're not one of them. Curiosity is not an immutable trait. You can learn how to be more curious.
Curiosity is contagious. Follow curious people, and you'll likely catch the bug. Once your mind is infected with a framework for curiosity, you instinctively apply that framework to everything.
Mystery is the key to curiosity. Embrace feeling puzzled because puzzles are everywhere. Question absolutes and axioms, even the ones in this article. Devise experiments to test premises.
Learn to recognize when your curiosity ebbs. I know that my curiosity is critically low whenever I stop reading consistently. I'm not talking about reading code — I mean reading anything. It's a case of intellectual malnutrition, and the cure is a content-rich diet.
Get into a habit of passive consumption. Listen to podcasts during your commute or household chores. Watch documentaries during your lunch break. If your mind wanders, let it. Don't focus on one topic. A diverse content diet is more likely to reveal a mystery that stirs you.
Actively consume content about mysteries that particularly draw your attention. Read long-form articles and books. Listen intently and take notes. But refrain from forcing yourself into active consumption. Forced activity stifles joy.
Unlike diets that nourish our bodies, a curiosity-inducing content diet has no plan and no schedule. Explore topics haphazardly, but also explore them deeply. Don't delay your curiosity. Pursue topics that interest you right now.
Get lost in rabbit holes, but avoid ones generated algorithmically. Algorithms have a knack for surfacing similar content. Actively search for answers to questions that arise.
Curiosity is a tug-of-war between selfishness and humility. You're compelled by a primal urge to know more. Yet learning requires you to expose a gap in your understanding, to recognize that there is still more to be known.
Learning is a conversation. Participate in that conversation. Ask questions. A good question synthesizes existing knowledge and addresses an acknowledged gap in understanding.
Getting better at being curious requires practicing curiosity. Fortunately, coding is full of puzzles. There are packages and tools to discover, languages to learn, and implementation details to explore.
Here are two techniques I've used to practice curiosity while coding.
Push boundaries and perform experiments.
I once worked as a programmer for a commercial audio/visual installation company. On one job, we installed a distributed video switching system controlled by an app. The app sent TCP packets from an iPad to a network switch and used VLAN untagging to connect video receivers on the network to broadcasts streamed by transmitters.
The app was incompatible with the latest switch firmware. The IT department refused to downgrade the firmware, and the app developer couldn't deliver a patch until well after the project deadline. But I could configure the app to send commands to any IP address.
I could write a TCP server to accept commands from the app, convert them to the network switch's protocol, and forward them to the switch. I needed a machine to host the script. The audio processing unit (APU) was a Linux box with plenty of storage and memory. Installing a custom script would void the warranty.
The APU was programmed by a drag-and-drop visual coding interface. One of the available "blocks" could run Lua scripts inside the application's sandbox. But, could a script running in the sandbox receive commands from the app and communicate with the network switch?
There were no documented restrictions, so I ran an experiment. It worked! Even better, script blocks automatically started whenever the APU booted. Video switching worked effortlessly even after reboots from power outages and other incidents.
My curiosity paid off. We completed the job on time. My discoveries created new solutions for clients and spawned internal tools that saved time and money on installations.
Destruction can be productive.
I enjoy finding ways to "break" a language. It isn't about finding bugs. It's about finding code examples that exhibit surprising behavior. It always starts with a question.
"How does a Python set determine if two objects are distinct?"
Knowing where to start looking for answers is a crucial skill. The Python docs on sets are pretty good.
"Oh, ok. Sets use an algorithm that checks if two objects have the same hash value and compare equal to each other. If both checks are true, then those objects are indistinguishable from each other, and the set can only contain one of them."
A little bit of knowledge opens the door to new questions.
"Can you 'trick' a set into thinking two different objects are nondistinct?"
Now there's a fun little puzzle. Think about it. An integer hashes to its value. You can control a custom object's hash value with the .__hash__()
method. That means you can create an object that hashes to the same value as the integer 1:
class NotOne:
def __hash__(self):
return 1
To confuse a set into thinking NotOne
instances are equal to 1
, you need them to compare equal to 1
. You can make NotOne
objects compare equal to any object by implementing an .__eq__()
method that always returns True
:
class NotOne:
def __hash__(self):
return 1
def __eq__(self, other):
return True
Now see what happens when you create a set containing 1
and an instance of NotOne
:
>>> n = NotOne()
>>> # Create a set S containing 1 and n
>>> S = {1, n}
>>> # S only contains 1
>>> S
{1}
>>> # But somehow n is in S!
>>> n in S
True
n
is very much a distinct object from 1
. n
isn't even a number. It doesn't support arithmetic operations. But you'll never be able to put n
and 1
in a set together because they fail to meet a set's criteria for distinctness. It feels weird that any set containing 1
also contains n
.
"That's pretty weird. I wonder if there's a way to trick a set into thinking two nondistinct objects are distinct from each other?"
For such a thing to be possible requires an object that doesn't compare equal to itself. If you've ever worked with IEEE 754 NaN objects before, you know they fit the bill.
"What happens when you try to put several NaN values into a Python set?"
Let's find out.
>>> S = {float("nan"), float("nan"), float("nan")}
>>> S
{nan, nan, nan}
"Ok, that is weird. But surely you can verify that the set contains a NaN object. Right?"
Behold:
>>> float("nan") in S
False
I love examples like this. In all honesty, nothing is broken — except maybe my brain for a little bit. Seeking out and understanding examples like these strengthens your intuition about code and surfaces new ideas for solving problems.
Start practicing curiosity today. Ask more questions and do more experiments. Be thankful that it's impossible to know everything. There's always a new puzzle to be solved. There are always new things to wonder about.
Are you interested in learning something new? I offer private one-on-one coaching for Python programming and technical writing. Click here for more information.
]]>When Apple released the M1 iPad Pros in March of 2021, I traded in my MacBook Pro to get the latest tablet. I’d already been using a mac Mini as my daily workhorse, so I wasn’t concerned about my day-to-day coding workflow. But I was very curious to know what coding on the iPad looks like, and if a full-featured professional coding set-up was even possible.
While a native Python IDE experience is still unavailable for iPadOS — and might never be — it turns out that it’s actually pretty easy to code in Python on the iPad, especialy if you’re willing to work in Jupyter Notebooks. You don’t even need an iPad Pro!
Here are five ways you can code in Python on any iPad right now.
My goto app for using Python on the iPad is Nicolas Holzschuch’s fantastic a-Shell app. a-Shell gives you a Unix-style terminal on your iPad or iPhone and it’s completely free. It’s also quite powerful.
Click here to open a-Shell in the Apple Store \(\rightarrow\)
Once you install and open a-Shell, type help
to get a quick overview of the app:
a-Shell comes with vim
and ed
for editing files, and it includes Python 3.9 out-of-the box. Here’s what editing a Python file in vim
looks like:
Very nice!
Esc
key. This makes working in vim painful until you figure out that Cmd + .
works like Esc
. If you'd like, you can change Caps Lock
to work as Esc
in a-Shell’s settings.Esc
in the Settings app.a-Shell plays nicely with iPadOS 15’s multi-tasking features. You can open new windows, put a-Shell side-by-side with another app, and — my favorite — use a-Shell in slideover mode.
I do a lot of reading on my iPad. When I come across something that I want to check in the Python REPL, it’s incredibly helpful to be able to swipe in from the right-hand-side of my iPad, quickly check something in the REPL, and then dismiss the app by swiping right:
You can install Python packages using pip
in a-Shell as long as those package are pure Python. This is admittedly a serious limitation for a lot of folks, but it does allow you to install some pretty awesome packages — including Will McGugan’s awesome rich library:
Besides being a great way to use Python on your iPad, a-Shell has a lot of other useful features. You can navigate your iPad’s file system, transfer files using curl
, generate SSH keys, SSH into remote servers, and more. You can even write programs in C and C++ and run them on your iPad 🤯
One of a-Shell’s major downsides is the lack of support for Python virtual environments. This means a-Shell is great for testing things out, or for doing some basic, pure-Python programming, but it’s not very well suited to professional development.
Carnets is a free, standalone Jupyter notebook app available on iPad and iPhone. You get a full Python and Jupyter implementation — no need to connect to an external Jupyter server — as well as a handful of useful Python packages, including NumPy, pandas, and Matplotlib.
Click here to view Carnets on the App Store \(\rightarrow\)
You can create, view, and edit notebook files, including ones that you created elsewhere or were sent to you by a colleague. The thing that I like most about it is that it “just works.” Download the app and in a few minutes you’re running Jupyter notebooks right on your iPad.
Carnet’s interface looks just like Jupyter in a browser. But what you see is what you get. There aren’t any bells and whistles here.
If you need to install a package that doesn’t come with Carnets, you can use %pip install
inside of a notebook cell to install the package:
To see all of the Python packages you get with Carnets, run %pip list
. There are quite a few — although many you see in the following list were installed by me or as dependencies of packages I installed:
There are two versions of Carnets available in the App Store:
1. Carnets – Jupyter
2. Carnets – Jupyter (with scipy)
Carnets – Jupyter (with scipy) includes a few additional packages for doing machine learning right on your iPad: scipy
, seaborn
, sklearn
, and coremltools
. If you can afford the extra space, I highly recommend downloading Carnets – Jupyter (with scipy) instead of the base Carnets app.
Like a-Shell, the Carnets app doesn’t let you create isolated Python environments. You’re stuck using the global environment and whatever package versions come pre-built with the app.
Juno is another Jupyter notebook app for the iPad that bills itself as a Jupyter IDE. Like Carnets, you get Python bundled with some extra packages. Unlike Carnets, Juno costs $15 and comes with some nice bonus features.
Click here view Juno on the App Store \(\rightarrow\)
Juno really stands out from Carnets with its sleek and modern iPad interface:
You can run cells by pressing Shift + Enter
inside of the cell or by tapping the blue Run Cell button at the bottom right-hand corner of the screen.
The lightning bolt button gives you quick access to some common tasks. You can change the cell type from code to Markdown, move cells up and down, and cut, copy, paste, and delete cells:
While Carnets can open Jupyter notebooks from anywhere on your iPad, the interface isn’t very iPad-friendly. Contrast this to Juno’s file picker, which really feels at home on iPad and iOS devices:
Another area where Juno shines is the IDE-like tab completion and tooltips that come built-in:
Like Carnets, Juno comes with a suite of built-in packages. But you can’t run %pip list
in a cell to see them all like you can in Carnets:
Package management in Juno is actually a bit of a disappointment, especially for an app that costs $15.
I couldn’t find a complete list of packages that come pre-installed with Juno, but here are a few imports that worked out of the box:
To install a package, click the name of the notebook at the top-center of the screen and select Install Python Package. You’ll see the following dialog box:
Like Carnets and a-Shell, you can only install pure Python packages. But unlike Carnets and a-Shell, any dependencies of a package you install won’t be installed automatically. You’ll need to install them manually one-by-one.
One nice thing about Juno’s package manger is that you get a chance to see some metadata for a package before you install it, including dependencies — which you can install at the touch of a button:
As I mentioned before, you can’t use %pip list
in Juno to view the packages you have installed into Juno’s environment. In fact, there is no way to view your installed packages from within Juno.
Instead, you must open the Files app and navigate to the site_packages/
folder in Juno’s on-device storage:
If you want to remove one of your installed packages, you need to do that manually from within site_packages/
. I find this to be a major shortcoming. I really appreciate the quick access to pip
using the %pip
magic command supported by Carnets.
Despite the clunky package manager, Juno does look much nicer than Carnets and the tab-completion and tooltips do boost productivity. If those features matter to you and you’re willing to fork over the $15, then Juno is a nice option.
Juno Connect is a Jupyter notebook client app that can be used to access Jupyter on a remote server. So, technicaly, Python isn’t running on your iPad, but Juno Connect provides a beautiful interface for working with remote notebook servers. You can purchase it from the App Store for $10.
Click here to view Juno Connect on the App Store \(\rightarrow\)
When you first launch Juno Connect, you’ll a see a screen with some notebooks ready to try out, as well as several options to connect to remote notebook servers:
Juno Connect support services like Cocalc and Binder right out of the box. You can also connect to Jupyter servers hosted elsewhere, such as on a Digital Ocean droplet.
When you connect to a notebook server, you’ll see a screen like the one below, where you can create a new notebook or select one to open:
Once you open or create a new notebook, you’ll see an interface that looks exactly like the typical Juno interface. And since the notebook is running on an external server, you get access to all of the typical Jupyter features, including the %pip
magic that doesn’t work with the local-only version of Juno:
One nice feature of Juno Connect is the ability to export a remote notebook to a local file on your iPad:
You can even export the notebook as a new notebook, allowing you to save a local copy to work with offline in either Juno or the Carnets app.
The last option on my list isn’t an iPad app and it doesn’t run Python locally on your iPad, but it’s absolutely essential if you need access to a full-blown development environment. That solution is the Codeanywhere Cloud IDE.
Plans start at $6 per month and you can get a 40% discount if you prepay for two years. This unlocks a VS Code style IDE that runs entirely in your browser.
Click here to get a 10% discount off any Codeanywhere subscription \(\rightarrow\)
Once you choose a plan and create an account, you’ll be taken to your dashboard where you can create new containers for working on projects:
When you click on New Container, you’ll have the opportunity use a blank Ubuntu Linux container or to select from some pre-defined containers with various languages pre-installed:
Once you create a container, it takes a few minutes for it to spin-up and become available to you in your dashboard:
The resources you get per container depend on the plan you selected. My plan gives me 15GB of storage and 4GB of memory.
Click the green Open IDE button to launch the IDE in a new browser tab:
If you’re familiar with VS Code, you’ll feel right at home in Codeanywhere’s IDE. It’s incredibly full-featured, including things like linter and debugging support:
You can even install extensions!
Codeanywhere is pricy compared to the other options mentioned in this list. It also requires an internet connection. But, I can’t live without it. It’s the best way I’ve found to do some hardcore coding on my iPad.
The five tools I've mentioned are what I currently use day-in and day-out to run Python on my iPad, but there are a number of other options that deserve a mention.
Whenever I bring up coding in Python on the iPad, I get a flurry of comments praising the Pythonista3 app. There was a time when Pythonista3 was a great option, but that’s no longer the case in my opinion.
First of all, Pythonista3 appears to be completely abandoned. The last version was released over a year ago before iPadOS 14 was available (the current version of iPadOS at the time of writing is 15). And, second, Pythonista3 only supports Python 3.6, which reached it’s end of life in December 2021.
The PyTo app is a serious contender in the Python-for-iPad space. You get Python 3.10 running locally with a nice IDE-style experience, support for editing multiple files, several bonus packages including NumPy, SciPy, Scikit-learn, and statsmodels. The full app experience costs $10.
I'm currently giving PyTo a run for it's money to see how it fits into my daily use. If it lives up to it's promise, you might find it higher up on the list in a future update to this article!
Google’s Colab is a browser-based notebook based on Jupyter notebooks. Your code runs on a private virtual machine that comes with most of the Python scientific stack pre-installed. You even get free access to GPUs, which makes Colab a great option for machine learning projects. If you need guaranteed uptime and more power, check out Colab Pro.
If you don't need Python running locally but want a solid IDE experience, check out vscode.dev. You can quickly clone an external code repository and start coding with Python right from your browser. The Python language doesn't have first-class support yet — stick with "webby" languages like JavaScript and CSS for the best experience — but it could be a good, free alternative in a pinch.
I've never used GitHub's Codespaces project, but if you have a GitHub organization on the Team or Enterprise plan, this might be a good option. It looks very similar to Codeanywhere.
The pyodide project brings Python and over seventy-five package in Python's scientific stack to any browser by compiling them all to WebAssembly. You can try pyodide in a REPL and bookmark it for quick access in the future.
Another option is to connect your iPad to a Raspberry Pi via USB, which allows you to access the Raspberry Pi as an ethernet device. This is really cool, but personaly I don’t want to have to carry any more devices around with me than I need to. It does make for a fun little project, though, and gets you access to a full local development server.
I’d love to see a native version of an IDE like VS Code on the iPad, but I don’t think it’s ever going to happen. However, between a-Shell, Carnets, and Codeanywhere, I’ve been able to do everything that I’ve needed to from my iPad. Of course, your mileage may vary.
So, in 2022, coding in Python on the iPad is not only feasible, it’s actually quite fun!
Get a curated list of five curiosity-inducing articles and resources each week from around the Python and Julia communities by subscribing to my Curious About Code newsletter.
And if you're interested in taking your coding skills to the next level, I offer private one-on-one coaching for Python programming and technical writing.
]]>If you've done any coding in Python, there's a good chance that you've used a number in one of your programs. For instance, you may have used an integer to specify the index of a value in a list or a floating-point number to represent an amount of currency.
But there's much more to numbers in Python than just their raw values. Let's look at three things about numbers in Python that you might not be aware of.
Pretty much everything in Python is an object. One of the first objects you learn about in Python is the str
object for representing strings. Maybe you've seen how strings have methods, such as the .lower()
method, which returns a new string with all lower case characters:
>>> "HELLO".lower()
'hello'
Numbers in Python are also objects and, just like strings, have their own methods. For instance, you can convert an integer to a byte string using the .to_bytes()
method:
>>> n = 255
>>> n.to_bytes(length=2, byteorder="big")
b'\x00\xff'
The length
parameter specifies the number of bytes to use in the byte string, and the byteorder
parameter determines the order of the bytes. For example, setting byteorder
to " big"
returns a byte string with the most significant byte first, and setting byteorder
to " little"
puts the least significant byte first.
255 is the largest integer that can be represented as an 8-bit integer, so you can set length=1
in .to_bytes()
with no problem:
>>> n.to_bytes(length=1, byteorder="big")
b'\xff'
If you set length=1
in .to_bytes()
for 256, however, you get an OverflowError
:
>>> n = 256
>>> n.to_bytes(length=1, byteorder="big")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: int too big to convert
You can convert a byte string to an integer using the .from_bytes()
class method:
>>> int.from_bytes(b'\x06\xc1', byteorder="big")
1729
Class methods are called from a class name instead of a class instance, which is why the .from_bytes()
method is called on int
above.
Floating point numbers also have methods. Perhaps the most useful method for floats is .is_integer()
which is used to check whether or not a float has no fractional part:
>>> n = 2.0
>>> n.is_integer()
True
>>> n = 3.14
>>> n.is_integer()
False
One fun float method is the .as_integer_ratio()
method, which returns a tuple containing the numerator and denominator of the fraction representing the floating point value:
>>> n.as_integer_ratio()
(1, 2)
Thanks to floating point representation error, though, this method can return some unexpected values:
>>> n = 0.1
>>> n.as_integer_ratio()
(3602879701896397, 36028797018963968)
If you need to, you can call number methods on number literals by surrounding the literals with parentheses:
>>> (255).to_bytes(length=1, byteorder="big")
b'\xff'
>>> (3.14).is_integer()
False
If you don't surround an integer literal with parentheses, you'll see a SyntaxError
when you call a method — although, strangely, you don't need the parentheses with floating-point literals:
>>> 255.to_bytes(length=1, byteorder="big")
File "<stdin>", line 1
255.to_bytes(length=1, byteorder="big")
^
SyntaxError: invalid syntax
>>> 3.14.is_integer()
False
You can find a complete list of methods available on Python's number types in the docs.
In mathematics, numbers have a natural hierarchy. For example, all natural numbers are integers, all integers are rational numbers, all rational numbers are real numbers, and all real numbers are complex numbers.
The same is true for numbers in Python. This “numeric tower” is expressed through abstract types contained in the numbers
module.
Every number in Python is an instance of the Number
class:
>>> from numbers import Number
>>> # Integers inherit from Number
>>> isinstance(1729, Number)
True
>>> # Floats inherit from Number
>>> isinstance(3.14, Number)
True
>>> # Complex numbers inherit from Number
>>> isinstance(1j, Number)
True
If you need to check if a value in Python is numeric, but you don't care what type of number the value is, use isinstance(value, Number)
.
Python comes with four additional abstract types whose hierarchy, beginning with the most general numeric type, is as follows:
Complex
class is used to represent complex numbers. There is one built-in concrete Complex
type: complex
.Real
class is used to represent real numbers. There is one built-in concrete Real
type: float
.Rational
class is used to represent rational numbers. There is one built-in concrete Rational
type: Fraction
.Integral
class is used to represent integers. There are two built-in concrete Integral
types: int
and bool
.Wait. bool
values are numbers?! Yes! You can verify all of this in your REPL:
>>> import numbers
>>> # Complex numbers inherit from Complex
>>> isinstance(1j, numbers.Complex)
True
>>> # Complex numbers are not Real
>>> isinstance(1j, numbers.Real)
False
>>> # Floats are Real
>>> isinstance(3.14, numbers.Real)
True
>>> # Floats are not Rational
>>> isinstance(3.14, numbers.Rational)
False
>>> # Fractions are Rational
>>> from fractions import Fraction
>>> isinstance(Fraction(1, 2), numbers.Rational)
True
>>> # Fractions are not Integral
>>> isinstance(Fraction(1, 2), numbers.Integral)
False
>>> # Ints are Integral
>>> isinstance(1729, numbers.Integral)
True
>>> # Bools are Integral
>>> isinstance(True, numbers.Integral)
True
>>> True == 1
True
>>> False == 0
True
At first glance, everything seems right here — other than bool
values being numbers, perhaps.
bool
type is Integral
— in fact, bool
inherits directly from int
— you can do some pretty weird stuff with True
and False
.True
as an index to the get the second value of an iterable. If you divide a number by False
you get a ZeroDivisionError
error."False"[True]
and 1 / False
in your REPL!Take a closer look, though, there are a couple of things that are a bit weird about Python's numeric hierarchy.
There are four concrete numeric types corresponding to the four abstract types in Pythons number tower: complex
, float
, Fraction
, and int
. But Python has a fifth number type, the Decimal
class, that is used to exactly represent decimal numbers and overcome limitations of floating-point arithmetic.
You might guess that Decimal
numbers are Real
, but you'd be wrong:
>>> from decimal import Decimal
>>> import numbers
>>> isinstance(Decimal("3.14159"), numbers.Real)
False
In fact, the only type that Decimal
numbers inherit from are Python’s Number
class:
>>> isinstance(Decimal("3.14159"), numbers.Complex)
False
>>> isinstance(Decimal("3.14159"), numbers.Rational)
False
>>> isinstance(Decimal("3.14159"), numbers.Integral)
False
>>> isinstance(Decimal("3.14159"), numbers.Number)
True
It makes sense that Decimal
doesn't inherit from Integral
. To some extent, it also makes sense that Decimal
doesn't inherit from Rational
. But why doesn't Decimal
inherit from Real
or Complex
?
The answer lies in the CPython source code:
Decimal has all of the methods specified by theReal
abc, but it should not be registered as aReal
because decimals do not interoperate with binary floats (i.e.Decimal('3.14') + 2.71828
is undefined). But, abstract reals are expected to interoperate (i.e. R1 + R2 should be expected to work if R1 and R2 are both Reals).
It all boils down to implementation.
On the one hand, floats implement the Real
abstract base class and are used to represent real numbers. But thanks to finite memory constraints, floating-point numbers are merely finite approximations of real numbers. This leads to confusing examples, like the following:
>>> 0.1 + 0.1 + 0.1 == 0.3
False
Floating-point numbers get stored in memory as binary fractions, but this causes some problems. Just like the fraction \(\frac{1}{3}\) has no finite decimal representation — there are infinitely many threes after the decimal point — the fraction \(\frac{1}{10}\) has no finite binary fraction representation.
In other words, you can't store 0.1 on a computer with exact precision — unless that computer has infinite memory.
From a strictly mathematical standpoint, all floating-point numbers are rational — except for float("inf")
and float("nan")
. But programmers use them to approximate real numbers and treat them, for the most part, as real numbers.
float("nan")
is a special floating point value representing "not a number" values — often abbreviated as NaN
values. But since float
is a numeric type, isinstance(float("nan"), Number)
returns True
.Floats are weird.
Python's abstract numeric base types allow you to create your own custom abstract and concrete numeric types.
As an example, consider the following class ExtendedInteger
which implements numbers of the form \(a + b\sqrt{p}\) where \(a\) and \(b\) are integers and \(p\) is prime (note that primality is not enforced by the class):
import math
import numbers
class ExtendedInteger(numbers.Real):
def __init__(self, a, b, p = 2) -> None:
self.a = a
self.b = b
self.p = p
self._val = a + (b * math.sqrt(p))
def __repr__(self):
return f"{self.__class__.__name__}({self.a}, {self.b}, {self.p})"
def __str__(self):
return f"{self.a} + {self.b}√{self.p}"
def __trunc__(self):
return int(self._val)
def __float__(self):
return float(self._val)
def __hash__(self):
return hash(float(self._val))
def __floor__(self):
return math.floot(self._val)
def __ceil__(self):
return math.ceil(self._val)
def __round__(self, ndigits=None):
return round(self._val, ndigits=ndigits)
def __abs__(self):
return abs(self._val)
def __floordiv__(self, other):
return self._val // other
def __rfloordiv__(self, other):
return other // self._val
def __truediv__(self, other):
return self._val / other
def __rtruediv__(self, other):
return other / self._val
def __mod__(self, other):
return self._val % other
def __rmod__(self, other):
return other % self._val
def __lt__(self, other):
return self._val < other
def __le__(self, other):
return self._val <= other
def __eq__(self, other):
return float(self) == float(other)
def __neg__(self):
return ExtendedInteger(-self.a, -self.b, self.p)
def __pos__(self):
return ExtendedInteger(+self.a, +self.b, self.p)
def __add__(self, other):
if isinstance(other, ExtendedInteger):
# If both instances have the same p value,
# return a new ExtendedInteger instance
if self.p == other.p:
new_a = self.a + other.a
new_b = self.b + other.b
return ExtendedInteger(new_a, new_b, self.p)
# Otherwise return a float
else:
return self._val + other._val
# If other is integral, add other to self's a value
elif isinstance(other, numbers.Integral):
new_a = self.a + other
return ExtendedInteger(new_a, self.b, self.p)
# If other is real, return a float
elif isinstance(other, numbers.Real):
return self._val + other._val
# If other is of unknown type, let other determine
# what to do
else:
return NotImplemented
def __radd__(self, other):
# Addition is commutative so defer to __add__
return self.__add__(other)
def __mul__(self, other):
if isinstance(other, ExtendedInteger):
# If both instances have the same p value,
# return a new ExtendedInteger instance
if self.p == other.p:
new_a = (self.a * other.a) + (self.b * other.b * self.p)
new_b = (self.a * other.b) + (self.b * other.a)
return ExtendedInteger(new_a, new_b, self.p)
# Otherwise, return a float
else:
return self._val * other._val
# If other is integral, multiply self's a and b by other
elif isinstance(other, numbers.Integral):
new_a = self.a * other
new_b = self.b * other
return ExtendedInteger(new_a, new_b, self.p)
# If other is real, return a float
elif isinstance(other, numbers.Real):
return self._val * other
# If other is of unknown type, let other determine
# what to do
else:
return NotImplemented
def __rmul__(self, other):
# Multiplication is commutative so defer to __mul__
return self.__mul__(other)
def __pow__(self, exponent):
return self._val ** exponent
def __rpow__(self, base):
return base ** self._val
You need to implement lots of dunder methods to ensure the concrete type implements the Real
interface. You also have to consider how methods like .__add__()
and .__mul__()
interact with other Real
types.
With ExtendedInteger
implemented, you can now do things like this:
>>> a = ExtendedInteger(1, 2)
>>> b = ExtendedInteger(2, 3)
>>> a
ExtendedInteger(1, 2, 2)
>>> # Check that a is a Number
>>> isinstance(a, numbers.Number)
True
>>> # Check that a is Real
>>> isinstance(a, numbers.Real)
True
>>> print(a)
1 + 2√2
>>> a * b
ExtendedInteger(14, 7, 2)
>>> print(a * b)
14 + 7√2
>>> float(a)
3.8284271247461903
Python's number hierarchy is quite flexible. But, of course, you should always take great care when implementing types derived from built-in abstract base types. You need to make sure they play well with others.
There are several tips in the docs for type implementors that you should read before implementing custom number types. It's also helpful to peruse the implementation of Fraction
.
So there you have it. Three things (plus a whole lot more, maybe) that you might not have known about numbers in Python:
Decimal
and float
.I hope you learned something new!
Are you looking to take your Python to the next level? I offer private one-on-one coaching. Click here for more information.
]]>Disclosure: some of the links in this article are affiliate links.
Learning a new skill is exciting. But learning how to learn is a skill that we're never really taught. This is a real tragedy and one that hasn't gone unnoticed.
There are plenty of self-help gurus with courses designed to help you learn how to learn. But there's a lot of noise in this space, in my opinion. A lot of fluff. I want to cut through that noise.
There's really only one thing you need to know.
One of the biggest myths about learning is the so-called 10,000-hour rule. This "rule" states that you need to accumulate approximately 10,000 hours of deliberate practice to become an expert in something. The idea is based on research by Swedish psychologist K. Anders Ericsson, who found that top performers — think chess grandmasters and musical virtuosos — trained for at least 10,000 hours before winning their first international competition.
The 10,000-hour rule was popularized by author Malcolm Gladwell in his 2007 book Outliers: The Story of Success. Author and business coach Josh Kaufman observed that a "society-wide game of telephone" began with this book. What started as a statement about top performers morphed into a "rule" about learning in general. And it's patently untrue.
What the 10,000-hour rule gets right, though, is the importance of practice. But you don't need to practice for 10,000 hours to learn something new. Even K. Anders Ericsson observed that it takes a surprisingly short amount of time to become reasonably good at something:
Let's imagine you are learning to play golf for the first time. In the early phases, you try to understand the basic strokes and focus on avoiding gross mistakes (like driving the ball into another player). You practice on the putting green, hit balls at a driving range, and play rounds with others who are most likely novices like you. In a surprisingly short time (perhaps 50 hours), you will develop better control and your game will improve. (Source)
When you start to learn something new, you tend to improve exponentially. But, eventually, your improvement levels off, and even though you are still practicing, you aren't improving. This frustrating improvement plateau happens because you aren't practicing deliberately.
To keep improving, you need to develop the ability to self-edit. This requires repeating a task over and over again, recognizing small mistakes, and adjusting to correct them. Here's how K. Anders Ericsson, continuing the golf analogy, describes deliberate practice:
Your golf game now is a social outing, in which you occasionally concentrate on your shot. From this point on, additional time on the course will not substantially improve your performance, which may remain at the same level for decades.
Why does this happen? You don't improve because when you are playing a game, you get only a single chance to make a shot from any given location. You don't get to figure out how you can correct mistakes. If you were allowed to take five to ten shots from the exact same location on the course, you would get more feedback on your technique and start to adjust your playing style to improve your control. (Source)
I call this the repetition-refinement feedback loop, and I've experienced its power in my own life. Learn enough to recognize errors, repeat a task until mistakes become apparent, adjust performance to correct the mistakes. Rinse and repeat.
I firmly believe the repetition-refinement loop is the key to learning anything — and to continue to learn over a lifetime. So how do you do it? Throughout my years of learning, I've narrowed it down to five steps:
Let's pick apart each of the five steps in more depth.
No matter how large the task seems, every skill I've ever learned only required learning three to five things to get started. The trick is knowing what those subskills are. Do a bit of research and break the skill down into smaller subskills. Go as deep as you can and try to identify the essential skills you need to learn before starting your learning journey.
This isn't as easy as it sounds. Many skills are much easier to deconstruct with guidance from someone who knows more than you do. Find someone ahead of you on their learning path to help you identify the key things to focus on as a novice.
A great tool for deconstructing a skill is a mind map. A mind map is a visual way of organizing skills, topics, and ideas around a central concept. You only need some paper and a pencil to make one. If you like apps, I highly recommend MindNode.
Find two or three resources covering the subskills you identified in step one. These could be blog posts, books, online courses, notes from a class — anything that contains the fundamentals of the skill you want to learn.
The goal here is to learn just enough to self-edit. You need to be able to recognize when you're making a mistake. Don't get caught in tutorial hell. Learn a few things, then start practicing.
Take notes while you're learning the fundamentals. All you need is a pen and paper, but it's best if your notes are searchable so you can find what you're looking for in the future. If you have a smartphone, you might be able to take pictures of your notes and use text recognition to search them. Personally, I use the GoodNotes app on my iPad to take handwritten notes with the Apple pencil.
Test yourself often. Flashcards are a good way to create little quizzes that you can reuse. I use old-fashioned paper cards for this, but I know many people that prefer Anki.
Once you've learned a few fundamentals, it's time to get your hands dirty. Make some mistakes and break some things. It's all a normal part of the learning process!
The goal here is to become reasonably good at the skill. It's difficult to define what "reasonably good" means. In my experience, it boils down to being able to perform the skill without overthinking it. It does not mean performing the skill perfectly. Making mistakes at this point is perfectly OK.
Why fifteen hours? That's roughly 30 minutes a day for one month. Of course, the time it takes to be reasonably good at something will vary, but fifteen hours is usually enough time.
Don't cram your practice into long sessions. Instead, split it up across multiple days. Studies have shown that spaced practice leads to better long-term memory. On days that you aren't practicing one skill, practice another. Interleaving small, related skills improves your overall understanding by strengthening connections between the skills.
Keep a practice log and treat it like a journal. Record what you practiced, how you felt about it, what you did well and what you struggled with. I use Notion to log things like this, but good ol' paper and pencil work just as well.
A learning barrier is anything that keeps you from learning as efficiently as possible. Use your practice log to identify and track learning barriers. Make a note of anything that causes friction and experiment with ways to eliminate the problem.
There are four types of learning barriers:
Physical barriers include your location and your environment. Of course, the ideal location depends on the skill you're learning. For example, if you're training to become a competitive swimmer, you won't get far if you train in your bathtub.
Most of the skills I've learned involve a computer and require deep focus and concentration. I have difficulty concentrating with background noise, but a quiet location isn't always available to me. A good pair of noise-canceling headphones can transform a noisy environment into a calm, peaceful learning sanctuary.
Physical learning barriers are, in theory, the most manageable. Just change your location and environment. Finding what works best for you, though, requires experimentation.
Emotional barriers involve fears and anxieties you may have about learning. Learning exposes you to the unknown, and the unknown is scary. Everyone I know that took on learning something new was anxious about it, so if you experience anxiety, you're in good company.
Seek out a support group. They could be classmates or an online community. Despite its general tilt towards toxicity, even social media can provide a network of supporters.
Psychological barriers are things like ADD/ADHD, dyslexia, or autism. These are significant barriers to learning and the most difficult to control. The first step is to be aware of them. Talk to a medical professional if you believe you suffer from a psychological condition.
Physiological barriers, such as getting enough sleep and eating well, are, in my experience, easy to ignore. For example, if I don't set reminders for myself, I can easily skip a meal when caught up with a task.
Sleep is probably the most critical physiological barrier to control. When you sleep, your brain process everything you've learned that day and makes new connections in your brain. Studies have shown that exercise also plays a role in memory. I like to walk. Walking boosts my mood and improves my focus. It's also a great way to deal with frustration.
As soon as you know one thing about whatever you're learning, you know more than someone else who wants to learn but hasn't started. When you share this with someone else, you solidify your own knowledge. Teaching also exposes gaps in your understanding and helps you identify areas that need improvement.
Teaching is scary. I know because I've done it. A lot. When you teach, you are vulnerable. But you don't have to teach in-person in front of hundreds of people. You can teach via email or Twitter DMs. Join a community centered around whatever you're learning and help others.
I've shared my framework for learning. But a framework, however nice it looks on paper, is only worth its salt if it works in practice. So get to it, and let me know how it works for you!
Are you interested in learning something new? I offer private one-on-one coaching for Python programming and technical writing. Click here for more information.
]]>I recently came across a tweet with a screenshot from a calculus book that defines the derivative with no mention of a limit. Not even Leibniz’s differentials have a home here. And although the definition is quite verbose, I found that it makes the derivative feel natural — tangible even.
If you’ve taken a calculus course in the last 120 years or so, then you’ve undoubtedly encountered the concept of a limit. In fact, a quick search for the question “Why are limits important?” returns hundreds, if not thousands, of results stating that limits are an essential part of calculus. How could you possibly understand the subject without them?
It turns out that you can develop a great deal of calculus — at least the entire first-year curriculum — without limits. And doing so, in my opinion, provides a deeper understanding of the subject, its history, and a better appreciation for limits and why they are useful.
While statements professing that you can’t do calculus without limits surely have Leibniz turning in his grave, it’s not surprising that students come away with this mindset. Limits are everywhere in the calculus curriculum.
Limits are abstract, though, and students who struggle to understand them will likely struggle with all of calculus. Some students will be doomed to failure, which is a tragedy considering Newton had little more than an intuition about limits. It took nearly a century after Newton for Cauchy and Weierstrass to formalize limits into the ϵ-δ definition used today.
Definition. Let f(x) be a function defined on an open interval around a (note that f(a) need not be defined). Then we say that the limit of f(x) as x approaches a is L and write
$$ \lim_{x \to a} f(x) = L $$
if for every number ϵ > 0 there exists some number δ > 0 such that
$$ |f(a) - L| < \epsilon \hspace{6pt} \textrm{whenever} \hspace{6pt} 0 < |x - a| < \delta $$
What makes this definition difficult to understand? In my opinion, the reasons are twofold:
Math educators, aware of students’ difficulties with limits, have endured their own struggle to invent better ways of teaching the topic. In 1981, Jerrold Marsden and Alan Weinstein, professors at the University of California, Berkely, proposed a new method — don’t teach limits.
Calculus without limits isn’t new. Gottfried Leibniz devised calculus using differentials, which are infinitesimal positive quantities less than any real number. The method of exhaustion, developed independently by the ancient Greeks and the Chinese, can be used to find areas and volumes of round shapes, like circles and cones.
Marsden and Weinstein took the latter approach and adapted the method of exhaustion to differentiation. In the preface to their book Calculus Unlimited, on the page screenshotted in the tweet that caught my attention, the authors present instructors with the idea of overtaking [1].
Definition. Let f and g be real-valued functions with domains contained in ℝ, and let a be a real number. We say that f overtakes g at a if there is an open interval I containing a such that
There’s a lot of notation in that definition, but the idea lends itself to a natural geometric interpretation.
For example, draw the graphs of two functions f and g that intersect at some point whose x-coordinate is a. If, around some interval I on the x-axis, the graph of f is below the graph of g to the left of a above the graph of g to the right of a, then f overtakes g at a.
To define the derivative, Marsden and Weinstein look at the slopes of lines through a.
Definition: Let f be a function defined on an open interval containing a. Consider the family of lines lₘ(x) = f(a) + m(x-a). Suppose there is a number b such that
Then we say that f is differentiable at a, and that b is the derivative of f at a.
Like the definition of overtaking, Marsden and Weinstein’s definition of the derivative paints a convenient geometric picture. It even provides a tangible method to find the derivative of a function at some point using graphing software.
For instance, consider the graph of f(x) = x² and some point, say a = 1. Graph any line that passes through f(a) = 1² = 1. Now vary the slope of the line until you find a new line that neither overtakes f nor is overtaken by f. If you can find such a line, its slope is the derivative of f(x) at a.
If you play around with this method in graphing software long enough, you’ll notice that you need to zoom in very close to the point f(a) to get any semblance of precision. I found this to be a beautiful and natural manifestation of what the ϵ-δ definition of a limit represents — namely the concern with points on f arbitrarily close to the limit — but manages to obscure from students through its cryptic formulation.
This visual method for finding the derivative at a point isn’t rigorous. It also lacks precision for all but the nicest of functions at the nicest of points. It does emphasize something, however, that was lost on me as a student for a long time: the tangent line is special.
In chapter 2 of Marsden and Weinstein’s textbook, the authors introduce the notion of a transition point as a point at which something suddenly changes. There are countless examples of transition points in nature: sunrise is the transition point from day to night, 100 degrees celsius is the point at which liquid water transitions to water vapor (at sea level, anyway).
The tangent line can be thought of as the transition point between the set of all lines through a point a that are overtaken by f and the set of all lines through a that overtake f. Something special happens with the tangent line that splits the set of all lines through the point a into two disjoint sets.
Transition points are perhaps the foundational concept in Calculus Unlimited. Marsden and Weinstein frame integrals through the lens of transition points, as well, and the concept plays an important role in their proof of The Fundamental Theorem of Calculus.
Marsden and Weinstein mention in their preface that “as far as [they] know, [their] definition [of the derivative] has not appeared elsewhere” [1]. That may be true of their specific definition. Still, a method established by Apollonius of Perga and later re-discovered by John Wallis calculates the derivative in a manner that feels very much like Marsden and Weinstein’s approach [2].
Math historian John Suzuki, in his essay The Lost Calculus, explains how the definition of the tangent line used by Apollonius and Wallis can be described in modern terms:
Suppose we wish to find the tangent to a curve y = f(x) at the point (a, f(a)). The tangent liney = T(x) may be defined as the line resting on one side of the curve; hence weither f(x) > T(x) for all x ≠ a, or f(x) < T(x) for all x ≠ a. (This is generally true for curves that do not change concavity; if the curve does change concavity, we must restrict our attention to intervals where the concavity is either positive or negative.)
Suzuki explores the work on derivatives by René Descartes, Jan Hudde, and Isaac Barrow, all of whom approached the problem without the need for infinitesimals and limits. Barrow even proved a version of The Fundamental Theorem of Calculus [2]!
There are many things that I like about Marsden and Weinstein’s textbook. But I’m not a calculus student anymore. I’ve been out of the classroom for nearly a decade, so it’s hard for me to say how students would respond to these ideas.
Marsden and Weinstein’s definition of the derivative is verbose, and it doesn’t address the quantifier complexity introduced by the formal definition of a limit. However, it provides a geometric algorithm for finding the derivative and avoids introducing logic that feels circular.
Although their approach has its appeal, a glance through the book is enough to see how arduous the calculations are using the book’s definitions. Whatever is gained in intuition is offset by painfully boring algebraic manipulations. Not to mention that removing limits from calculus is a disservice to students who aspire to become mathematicians.
There is a lesson here, though, that shouldn’t be overlooked.
Newton’s and Leibniz’s ideas are indisputably important, but framing calculus as the study of limits gives students an inaccurate picture of what calculus really is. So much emphasis is placed on the limit that, at least in many students’ minds, limits become synonymous with calculus itself. That’s like saying that painting is the art of using a paintbrush!
Even worse, focusing on limits hides centuries of effort that provides historical context and validates students who struggle with the modern definitions of the limit and derivative.
Exploring derivatives without limits provides the opportunity to better understand the content of calculus, not just the tools, as well as the long history behind the problems calculus solves. In doing so, you might just come away with a better appreciation of why limits are useful. I certainly did.
If you find an error in this article, you can report it here.
[1] Marsden, Jerrold and Weinstein, Alan J. (1981) Calculus Unlimited. Benjamin/Cummings Publishing Company, Inc. , Menlo Park, CA. ISBN 0–8053–6932–5. https://resolver.caltech.edu/CaltechBOOK:1981.001
[2] Suzuki, Jeff. (2005). The Lost Calculus. Mathematics Magazine, vol. 78, (2005), pp. 339–353. https://www.maa.org/programs/maa-awards/writing-awards/the-lost-calculus-1637-1670-tangency-and-optimization-without-limits
The diagrams and figures in this article were created with Canva and Geogebra.
Some numbers are hard to compute.
In a 1990 article for Scientific American, mathematicians Ronald Graham and Joel Spencer quoted Paul Erdös as saying:
Suppose aliens invade the earth and threaten to obliterate it in a year's time unless human beings can find the Ramsey number for red five and blue five. We could marshal the world's best minds and fastest computers, and within a year we could probably calculate the value. If the aliens demanded the Ramsey number for red six and blue six, however, we would have no choice but to launch a preemptive attack.
It's an intriguing quote. But what the heck is a Ramsey number, and why is it so hard to calculate?
Let's start by playing a game.
The game is called Sim — a two-player game that you can play with pencil and paper. Every game of Sim is played on a "board" made from the six vertices of a hexagon:
Each player takes turns drawing an edge between a pair of vertices, but they do so with two different colors. For example, player one might use a blue pencil, and player two might use a red pencil.
The first player to join any two of their existing edges into a triangle immediately loses:
But will one player always lose? Or is it possible for the game to end in a draw? Play a few rounds of the game with a friend — or against a computer — and see if you can force a tie!
To determine whether or not Sim can end in a draw, let's think about a different question — one that, at first glance, might not seem related at all.
Your friend messages you and says, "I want to host a party for a group of randomly selected people, but I need to make sure that either: 1) there are three people who are all mutually strangers, or 2) there are three people who are all friends with each other. I'm serving dinner, and I'm on a tight budget. What's the smallest number of people I need to invite?"
Let's see if we can help your friend out.
Since we need to guarantee at least three mutual strangers or three people who are all friends, it seems reasonable that we need to invite at least three people to the party.
But three people isn't enough to guarantee your friend's conditions are met. For example, your friend might invite two people that know each other and one that is a stranger to the other two:
Four people won't work, either. Your friend could invite two people who are friends and two people who are strangers to each other but friends with one other person at the party:
What about five people? That feels more promising because even if your friend invites two people who are friends, the remaining three people could all be mutual strangers:
But the situation isn't so simple. There are lots of ways that five people could be in stranger/friend relationships with one another. For instance, you might find a group of three people out of the five where Person 1 knows Person 2 and Person 2 knows Person 3, but Person 1 doesn't know Person 3:
You can extend this situation to five people so that everyone at the party is friends with exactly two other people and a stranger to everyone else.
How? Well, let's represent each of the five people at the party as the vertex of a pentagon and use blue edges to indicate two people are friends and red edges to indicate two people are strangers.
Join each of the vertices around the face of the pentagon using blue edges. Then connect each pair of vertices that aren't joined by a blue edge with a red edge:
The diagram must have three vertices joined into a triangle with all blue edges to have three mutual friends. The same goes for three mutual strangers, except the edges of the triangle need to be red.
But there aren't any triangles in the diagram with three edges of the same color! So inviting five people to the party won't guarantee that your friend's conditions are met.
At this point, you might be wondering if there is any number of people your friend can invite to the party so that at least three people are mutual friends or three people are mutual strangers.
Does a similar diagram rule out six people? For example, what happens if you draw the vertices of a hexagon, join all of the vertices around the face with blue edges, and then connect everything else with red edges?
Well, look at that! Our first triangle! The strategy that worked for five people fails for six, but it doesn't mean that any group of six will necessarily contain three mutual strangers.
Do you notice any similarities between your friend's dinner party problem and the Game of Sim?
In both cases, we've drawn the vertices of a polygon and added blue and red edges. The difference is that in Sim, you try to avoid triangles with the same color edges, and in the dinner party problem, you want to form triangles whose edges are all the same color.
Although explaining the diagrams in terms of polygons helps us visualize the problem, it's not strictly necessary. What we've really been doing is drawing graphs — not the graphs of functions you might be familiar with from algebra, but a different kind of graph, comprised of vertices and edges. These graphs are studied in an area of mathematics called graph theory.
In particular, we've been drawing complete graphs, which are graphs with every possible edge between every pair of distinct vertices.
The Game of Sim and the dinner party problem are concerned with the inevitability of certain kinds of structures appearing whenever you color all the edges in a complete graph with two colors. These structures are called subgraphs.
A subgraph is a portion of a graph comprised of one or more of the vertices in the original graph and a subset of the edges in the original graph that connect the vertices in the subgraph.
If a subgraph is complete, it is called an \(n\)-clique, where n is the number of vertices in the subgraph.
Using the language of graph theory, you can reframe the dinner party problem as a question about graphs.
The Dinner Party Problem (Graph-Theoretical Version): Does a complete graph exist for which any red-blue coloring of its edges results in either a red 3-clique or a blue 3-clique? And if one does exist, what is the smallest one?
You can also reframe the question about whether or not the Game of Sim can end in a draw in graph-theoretical terms.
The Game of Sim (Graph-Theoretical Version): Does every red-blue coloring of the complete graph on six vertices contain either a red 3-clique or a blue 3-clique?
In 1928, British philosopher and mathematician Frank Ramsey published a paper called On a Problem in Formal Logic. The subject of Ramsey's paper bears little resemblance to the dinner party problem or the Game of Sim.
But one of the results in the paper — one that was not the focus of the discussion but needed in a more important argument — showed that for a sufficiently large system, no matter how chaotic, there exists a substructure with some kind of order. This minor result became one of Ramsey's most iconic legacies, and it can be stated in graph-theoretical terms.
Ramsey's Theorem (Graph-Theoretical Version): Given any two positive integers \(r\) and \(b\), there exists a minimum number \(R(r, b)\) such that any red-blue coloring of the complete graph on \(R(r, b)\) vertices contains either a red \(r\)-clique or a blue \(b\)-clique.
The number \(R(r, b)\) is called a Ramsey number, and the notation reminds us that Ramsey numbers depend on the choice of \(r\) and \(b\).
If you set both \(r\) and \(b\) to 3, then Ramsey's Theorem looks a lot like the dinner party problem. In fact, the theorem tells you that a solution to the problem must exist! And the smallest number of vertices you need — that is, the smallest number of people your friend needs to invite to the party — is \(R(3, 3)\).
Telling your friend to invite \(R(3, 3)\) people to their party isn't particularly helpful, though. It's good to know that they can solve their problem, but they need to know the value of \(R(3, 3)\).
The analysis we did for the dinner party problem tells us that \(R(3, 3)\) is at least six. But just because we found one red-blue coloring of the edges for the complete graph on six vertices with a red 3-clique doesn't mean that all red-blue edge colorings will have that property.
Let's look at the situation in a bit more depth. First, choose any vertex in the complete graph on six vertices and call it \(v\).
The vertex \(v\) has five edges connected to it. Now, pretend that you've colored all of the edges in the graph blue or red. What can you say about the number of red and blue edges connected to \(v\)?
There are six possibilities:
In all six cases, \(v\) is connected to at least three edges of the same color. For the sake of argument, assume that \(v\) is connected to at least three red edges. (If it isn't, swap red and blue in the following argument.) Let \(x\), \(y\), and \(z\) be the vertices at the other end of those edges.
In the diagram, there are three specific edges colored red, but we could have chosen any three of the five edges connected to \(v\).
If any of the edges \(xy\), \(xz\), or \(yz\) is red, then the coloring contains a red 3-clique.
On the other hand, if none of the edges \(xy\), \(xz\), or \(yz\) is red — in other words, they are all blue — then the coloring contains a blue 3-clique.
So, any red-blue edge coloring of the complete graph on six vertices inevitably contains either a red 3-cycle or a blue 3-cycle. This means that \(R(3, 3)\) is at most six, but since we already knew that \(R(3, 3)\) is at least six, we can conclude that \(R(3, 3)\) is exactly six!
This simultaneously solves the dinner party problem and whether the Game of Sim can end in a draw.
You can tell your friend that they only need to invite six people to the party to guarantee that at least three people are mutual friends or are mutual strangers.
And since the Game of Sim iteratively colors the edges of a complete graph on six vertices red or blue, at some point, one of the players will be forced to complete a triangle whose edges are the same color.
Interest in Ramsey's Theorem seems to have begun when the 1953 Putnam Competition included a graph-theoretic version of the dinner party problem. The solution to the problem established that \(R(3, 3)\) is less than six, and in 1955, mathematicians Robert Greenwood and Andrew Gleason proved that \(R(3, 3)\) is equal to six.
In the same paper, Greenwood and Gleason showed that \(R(4, 4)\) is eighteen using techniques considerably more advanced than what is needed for \(R(3, 3)\).
The search for \(R(5, 5)\), and Ramsey numbers for different values of \(r\) and \(b\), was on. In 1965, H. L. Abbott published the first lower bound for \(R(5, 5)\) in his doctoral thesis, setting the value to at least thirty-eight. By 1989, the value had been raised to forty-three.
The current best upper bound for \(R(5, 5)\) is forty-eight, and it took over a decade to lower that from the previous upper bound of forty-nine. So, after more than half a century, the best estimates for \(R(5, 5)\) are:
$$ 43 \leq R(5, 5) \leq 48. $$
The situation is even worse for \(R(6, 6)\). The current best estimates are:
$$ 102 \leq R(6, 6) \leq 165. $$
Why is calculating Ramsey numbers so hard, even for small values of \(r\) and \(b\)? It's all thanks to an explosion in the number of possible edge colorings as the number of vertices grows in the complete graphs that need to be checked.
Every vertex in a complete graph on \(n\) vertices is connected to \(n-1\) edges, so adding up the edges connected to each vertex gives \(n(n-1)\) edges. But each edge is connected to two vertices, so adding up the edges this way counts each edge twice. This means that the complete graph on \(n\) vertices has \(\frac{n(n-1)}{2}\) edges.
When you color each edge in a complete graph on \(n\) vertices red or blue, you have two choices of color for each edge. So, the total number of ways that you can color the edges of the graph is:
$$ \underbrace{2 \times 2 \times \cdots \times 2}_{\frac{n(n-1)}{2} \text{ times}} = 2^{\frac{n(n-1)}{2}}. $$
That's two to the power of \(\frac{n(n-1)}{2}\). In the best case — that is, if \(R(5, 5)\) is forty-three — you need to check 2^{903} edge colorings to verify that every single one has at least one red 5-clique or one blue 5-clique.
2^{903} is roughly 10^{271}. For comparison, astrophysicists estimate that the universe contains about 10^{80} atoms!
The number of edge colorings you'd need to check to compute \(R(6, 6)\) is even more staggering. At best, you'd have to check 2^{5151} — roughly 10^{1550} — edge colorings!
For the last half-century, mathematicians have spent significant resources checking edge colorings of the complete graph on forty-three vertices. Each edge coloring checked has contained either a red 5-clique or a blue 5-clique. There is a consensus that \(R(5, 5)\) is likely equal to forty-three.
But why haven't we found the value yet? Erdös posed his hypothetical scenario involving alien invaders in 1990. He thought that \(R(5, 5)\) could be found within a year, assuming the fastest computers and brightest minds were working on the problem.
Since 1990, computer processors have sped up exponentially, thanks in part to Moore's Law. This "law" states that the number of transistors on a microchip doubles roughly every two years.
If we assume that this means that processor speeds also double every two years, then modern processors in 2021 are approximately 2^{10} times faster than processors in 1990. Under these assumptions, a task that took a year to compute in 1990 should take about eight-and-a-half hours to compute today.
Of course, these are just rough estimates. They don't consider other limiting factors, such as interest in the problem and access to sufficient computing resources. And — perhaps most importantly — we haven't had an alien force threatening to obliterate us if we don't compute \(R(5, 5)\) quickly enough.
Given the exponential increase in processing power, though, and the prior work on \(R(5, 5)\), it seems feasible that we could meet the aliens' demands, perhaps in much less time than a year.
What about \(R(6, 6)\), though?
To give you some idea of how large the number of edge colorings that need to be checked is, consider that the universe has been around for about 13.77 billion years. There are 86,400 seconds in a day and, using the Gregorian year of 365.2425 days, a total of 31,556,952 seconds every year.
This means that approximately 4.34 \(\times\) 10^{17} seconds have elapsed since the big bang. That's 434 quadrillion seconds!
Using the best lower bound for \(R(6, 6)\), the best-case scenario means checking approximately 2^{5151} edge colorings. That's nearly 9.3 \(\times\) 10^{1532} times more edge colorings than seconds since the beginning of the universe!
Even if you could check one edge coloring every nanosecond, it would take you more than 2.94 \(\times\) 10^{1516} years to check them all! And, from the looks of it, you'd need a computer with more processors than there are atoms in the universe to leverage parallel computing to knock the time needed down to a year.
Not even quantum computing offers much hope. The best algorithm for the kinds of search needed to sift through edge colorings is called Grover's Algorithm, and it can search through a space of \(N\) items in about \(\sqrt{N}\) steps.
In other words, searching through the 2^{5151} possible edge colorings of the complete graph on six vertices would take about 2^{2576} steps. Assuming each of those steps took about a nanosecond, you still need about 9 \(\times\) 10^{758} years to search through all of the edge colorings.
The numbers are mind-numbingly huge. And while it's true that not all edge colorings need to be checked — after all, mathematicians have found ways to rule out certain configurations — the scale of the numbers is so massive that, even using our best computers, we'd need an earth-shattering breakthrough in mathematics if we are to find \(R(6, 6)\) in less than a year.
Let's hope Erdös's alien invaders never pay us a visit.
Was this article helpful or enjoyable to you? If so, consider supporting me. Every little bit helps motivate me to keep producing more content.
If you'd like to get notified whenever I publish new content, you can subscribe via RSS or join my email newsletter.
Many thanks to Tariq Rashid for reading an early draft of this article and providing valuable feedback.
If you find an error in this article, you can report it here.
Angeltveit, V., & McKay, B. (2018). R(5, 5) ≤ 48. Journal of Graph Theory, 89, 5– 13. https://doi.org/10.1002/jgt.22235
Graham, R., & Spencer, J. (1990). Ramsey Theory. Scientific American, 112–117.
Greenwood, R., & Gleason, A. (1955). Combinatorial Relations and Chromatic Graphs. Canadian Journal of Mathematics,7, 1–7. doi:10.4153/CJM-1955-001-4
McKay, B., & Radziszowski, S. (1997). Subgraph Counting Identities and Ramsey Numbers, Journal of Combinatorial Theory, Series B, 69, Issue 2, 193–209. https://doi.org/10.1006/jctb.1996.1741
Radziszowski, S. (2011). Small Ramsey Numbers. Dynamic Surveys. Electronic Journal of Combinatorics.
The diagrams and figures in this article were created with Canva and Excalidraw.
]]>Imagine meeting an extraterrestrial civilization and, for the sake of argument, let’s assume that you can somehow communicate with these beings. This civilization is a peculiar one in that it has no concept of what we call mathematics.
During some friendly conversation, you mention something about math.
Your curious alien companion stops you and asks, “What is this ‘mathematics’ that you speak of?”
“Well,” you begin, “mathematics is the study of…”
You pause.
What exactly is mathematics? How do you describe it?
This question has recently plagued me, and I fear I have no good answer. Fortunately, I’m not alone. Even Wikipedia recognizes that mathematics “has no generally accepted definition.”
As any good digital citizen would, I turned to Twitter to crowdsource an answer:
I didn’t put too much thought into my poll choices.
I chose Numbers because whenever I told people that I was studying mathematics, one of the most common responses was, “Oh? You must be really good with numbers!” (I’m not.)
Logic was meant to be a bit of a red herring. Mathematicians certainly use logic as a tool. But so do theoretical physicists and philosophers. Logicians study logic, and while there have been many logicians that study mathematics, the two pursuits have always been separate in my mind.
Finally, Quantitative ideas was a choice I threw in because it sounded reasonable. It’s general enough to capture a wide range of topics but still keeps the focus on numbers. This isn’t entirely accurate, though. Ask a topologist if their work is quantitative, and they’ll probably say, “Not really.”
To my surprise, Logic and Quantitative ideas received the same portion of votes. Those who answered Other commented with notions such as space, structure, and patterns.
What I hope that poll participants realized is that, to some degree, mathematics studies all of these things. Yet every answer to the question “What is mathematics the study of?” feels inadequate.
While pondering the definition of mathematics, one thing that struck me is that it seems possible to complete this exercise for other scientific disciplines. For example, you could say that physics is the study of how the universe works. Chemistry is the study of what the universe is made of. Biology is the study of living organisms.
I’d be a fool not to point out that these “definitions” are simplistic. Every human endeavor is fraught with nuance. But, to a certain extent, you can somehow distill these disciplines into a single sentence. This doesn’t appear to be the case with mathematics.
Throughout history, mathematics has been placed on a pedestal and given an almost god-like significance. One example of this is the oft-quoted article titled The unreasonable effectiveness of mathematics in the natural sciences penned in 1960 by physicist Eugene Wigner.
In his article, Wigner expresses his awe of mathematics’ applicability to physics:
“The miracle of the appropriateness of the language of mathematics for the formulation of the laws of physics is a wonderful gift which we neither understand nor deserve.”
Despite my love for mathematics, I find Wigner’s attitude a bit strange and borderline fanatical. In my mind, it’s not surprising that one can describe the observable universe in the language of mathematics. After all, mathematics was born in human minds attempting to understand the things they observed.
I’m no historian, but from what I’ve gathered, the advent of mathematics stems primarily from the need to measure quantity. As civilization formed and trade and economies developed, we humans needed to keep track of everything from land area to amounts of goods to currency.
Humans are an inquisitive bunch, though, and our capacity for abstraction is not a modern development. You can find examples in Indian texts from as early as 800 BCE of what we now call the Pythagorean Theorem.
The concept of number itself is a powerful abstraction. We don’t know who the first human was that recognized that three grains of sand and a group of three friends gathered together to have a shared three-ness. But we know that at some point, it did occur — likely a very long time ago and, I would guess, to multiple people independently.
What is clear to me, though, is that mathematics began with observations of our world. Necessity drove the invention of numerical systems and measurement. Curiosity carried the invention to new heights.
Greek mathematics is widely considered the birthplace of mathematical proof. Most of the accounts of the history of mathematics I've read put the Greeks at the center of modern mathematical thought. Whether or not this narrative is accurate is up for debate.
The story goes that Thales of Miletus learned mathematics from the Babylonians and Egyptians. Later, Thales brilliantly applied logic and reasoning to explain mathematics earning him the title of the first person to apply deductive reasoning to geometry.
Some modern scholars are skeptical of the Babylonian influence on Thales’ mathematics. But the Babylonian and Egyptian influence on Greek society is hardly controversial. And, as pointed out by Karine Chemla in her prologue to the book The History of Mathematical Proof in Ancient Traditions, “several reasons suggest that we should be wary regarding the standard narrative.”
For one, it appears that sources have gone missing from the historical record. Chemla offers as one piece of evidence how remarks made by Henry Thomas Colebrooke, the first person to translate ancient Sanskrit texts to European languages, seem to have been ignored by later historians:
“Colebrooke read in the Sanskrit texts a rather elaborate system of proof in which the algebraic rules used in the application of algebra were themselves proved. Moreover, he pointed resolutely to the use in these writings of ‘algebraic proofs’. It is striking that these remarks were not taken up in later historiography. Why did this evidence disappear from subsequent accounts?”
So, Indian mathematicians — and quite possibly Babylonian and Egyptian mathematicians — were doing mathematical proofs before the Greeks. They just didn’t look like Greek proofs, and historical bias has all but erased their contribution to this part of the development of mathematics.
What we can gather, though, is that at some time, mathematics moved from accounting and engineering to something more like philosophy. The statement of a mathematical principle must be accompanied by a proof explaining why that principle is true.
So what, then, is mathematics? Mathematics has exploded into something far beyond the geometry and algebra handed down to us from the ancients. Some of the objects studied by mathematicians are so abstract that they have no apparent example in nature.
This is where I have to come clean. I really don’t know what mathematics is. I know a few things that it is not, and a few things that it is in part.
The most significant difference I see between mathematics and sciences like physics and chemistry is that mathematics, although rooted in observations of our universe, is not concerned with material objects.
So perhaps mathematics is best described as the science of the immaterial universe — the universe of human thought. Just as physical science is divided into various disciplines — like astronomy or chemistry — so too can mathematics be broken down into things like geometry, algebra, and analysis.
In other words, I wonder if the word mathematics is more akin to the word science than it is physics. It is not a discipline in itself but encompasses many disciplines that share a common toolkit. Science has the scientific method, and mathematics has deductive logic.
Doesn’t this place mathematics into the realm of philosophy? Perhaps. But even philosophers struggle to explain mathematics. Stewart Shapiro, in the Oxford Online Handbook’s introduction to the philosophy of mathematics, has this to say about the trouble mathematics presents to philosophers:
“The conflict between rationalism and empiricism reflects some tension in the traditional views concerning mathematics, if not logic. Mathematics seems necessary and a priori, and yet it has something to do with the physical world. How is this possible? How can we learn something important about the physical world by a priori reflection in our comfortable armchairs? As noted above, mathematics is essential to any scientific understanding of the world, and science is empirical if anything is—rationalism notwithstanding.”
Is it this duality of rationalism and empiricism that plagues anyone who dares try to define mathematics? Maybe. I don’t know. I’m not sure I’ll ever know.
There is one thing I can say now that I’ve spent a couple of weeks thinking about the definition of mathematics. The ineffectiveness of any definition now seems to me to be perfectly reasonable.
]]>For the past few years, I've referred to myself as a recovering mathematician. Let me explain.
I fell in love with mathematics about a decade ago while taking a calculus class at my local community college. Yeah, I know. Calculus. Yuck. It wasn't so much the course content that I fell in love with, though, but the way the professor explained it to us.
Nothing was presented to us as fact. Everything was questionable. Everything was a question. The math, it seemed, wasn't that the derivative of $x^n$ is $nx^{n-1}$ but how you get there using the limit definition of a derivative.
I began to pay close attention when the professor wrote the word "proof" on the whiteboard. A story was about to be told.
College and I hadn't always gotten along. After high school, I spent about two months goofing off at The University of Texas at Austin "discovering myself."
I was supposed to study music, you see. I was going to be a jazz pianist. You know, like Thelonius Monk or something. But I liked minor seconds and tritones, and the piano professor at UT-Austin liked major thirds and perfect fourths.
Don't get me wrong; those are some beautiful intervals. But life isn't major and perfect. It's dirty and dissonant. And good jazz is rooted in Real Life™. And also Egyptian sun gods and trips to Saturn.
Well, the good professor disagreed. So I quit and switched my major to "general studies," where you learn nothing about everything. I spent my time skipping class to play music.
For the next couple of years, music was my mistress (may you rest in peace, Duke Ellington). I left UT and, at my mother's behest, took some classes in audio engineering at the Arlyn recording studio.
I graduated as a certified audio engineer of the studio arts and promptly started working as a live sound engineer in various nightclubs back in my hometown of Houston, where pretty much everything I had learned was useless.
I played the Wurlitzer electric piano in a duo with a drummer. He played in weird time signatures, and I forced the electrons from my Wurlitzer's pickups through fuzz and wah-wah pedals. I picked up a regular Thursday night gig with a cover band. We played things like Cannonball Adderley, Pink Floyd, and the Ghostbuster's theme song, all in the same set.
It was wild. It was fun.
Then one day, I got a call from this guy I'd gone to engineering school with. He'd landed an unpaid internship at the hottest studio in Hollywood: Oceanway. Radiohead had recorded Hail to the Thief there a couple of years earlier. Beyoncé and Beck were laying down tracks. And he was in the middle of it, putting all of his audio engineering knowledge to work getting coffee for producers or whatever.
He called me because he had met a band that needed a keyboardist for their latest album. I phoned the drummer, and he played some tracks for me, holding his phone up to the speaker because this was like 2005. It was British pop rock.
They agreed to pay for a plane ticket and put me up in their apartment for a week. I just had to pay for food. I'd never been to Los Angeles and didn't want to pass up the chance to step foot in a studio where Miles Davis and Frank Sinatra had recorded. So I agreed.
Six months later, I joined the band and moved to LA to be a rockstar. We called ourselves the Small Hours. And while we came close to getting close to making it — we played one show at the Viper room — I had more or less lost interest after a year of grinding the LA scene. And I'd fallen head over heels for a girl I met at my day job.
I worked shipping and receiving for a company that distributed the finest smoking accessories and hippie decor in Van Nuys. Like cheap acrylic bongs and poorly painted ceramic figurines of mushrooms and dragons.
A few months into the job, I met one of the girls that worked the drill press in the shop. She had gorgeous jet-black hair and a smile that made my knees weak. But she only spoke Spanish. And three years of high school Spanish class had somehow only prepared me to say "Hola, me llamo David" and then stare blankly and awkwardly.
I started studying Spanish. I bought a bilingual dictionary and began memorizing phrases to say each day. I enlisted the help of another woman who worked in the shipping area of the warehouse — and who knew a little bit of English and wanted to learn more — to help me practice Spanish.
Eventually, I worked up the courage to ask my crush on a date. I spent hours the night before memorizing Spanish phrases. The next day after work, I approached her and began to recite: "Hola. Yo quiero saber si usted quiere ir conmigo a ver una... una... peli... Ugh! I can't remember how to say movie!"
Fortunately, I had written everything down and taken it to work in my back pocket. I pulled out the paper and finished reading from my script. To my absolute shock, she agreed.
Three — yes, three — weeks later, we finally went on that date. We saw Ice Age 2.
Nine months later, and after nearly destroying my bilingual dictionary from so much daily use, I asked Raquel to marry me. And for reasons I'll never understand, she said yes.
I had a hobby that bewildered Raquel. I read science books. Like, textbooks. I bought logic puzzle books to work on in my spare time.
One day she had an idea. "Why don't you go back to school and study science?"
"Yeah, I guess so," I thought. "I like physics. Maybe I'll do that."
So, after moving back to Houston with Raquel and getting married in my parent's backyard, I started casually taking classes at the local community college. I planned to knock out some core classes and then transfer to the University of Houston and major in physics. Or maybe chemistry. I didn't know. They're both awesome.
I knew I'd have to get caught up in mathematics, no matter what science I decided to study. So I took a placement test and got placed in a college algebra class. It was fine. I passed without too much trouble. Then I took trigonometry and pre-calculus. I was doing well, but the math just felt like a means to an end.
Then I signed up for calculus, and my life changed. The professor was known around campus for being the most difficult math teacher. So I wasn't sure what to expect. I'd never taken calculus before, and I'd heard it was hard.
It turns out this professor taught calculus with proofs. I'd only seen proofs in my high school geometry class, and I remember them being an exercise in pedantry. But these proofs were different. They felt like stories.
The characters were the definitions of things like limits and derivatives. The stories became theorems, and we referred to them when telling other stories about continuous functions and convergent sequences. It was like we were building up an entire mythology of mathematical ideas from scratch.
I started to write miniature essays on my math tests. I spent as much time practicing taking derivatives as I did crafting written explanations for solutions.
I stopped reading science books and started reading math books. I found a used copy of a book called Mathematics for the Nonmathematician by Morris Kline. It told the story of mathematics in a way that was both engaging and thought-provoking. I'd never thought about math having a rich heritage, and I was intrigued with how integral mathematics was to the human experience.
Pretty soon, I had forgotten about physics and chemistry and knew exactly what I wanted to study: Math.
Around this time, my first daughter was born. Raquel and I started to get serious about establishing her permanently in the United States. See, Raquel didn't have any papers.
Thinking it would be easy for a US citizen to get permanent residency for his wife, we started meeting with immigration attorneys. We were wrong. Raquel had orders for deportation for failure to report to court after entering the US.
Not only would she have to leave the United States voluntarily, but she would also have to remain outside of the US for ten years before she'd be eligible to apply for a spouse visa. Because she was married to a US citizen, she could qualify for a pardon after five years, at which point she could then apply for permanent residency. All of this, of course, as long as the immigration laws didn't change.
We were devastated. But we had to do it.
Raquel moved back to her home country of El Salvador in 2008 with our one-year-old daughter. I took a semester off of school to move with them but eventually returned to the US to continue studying.
For the next five years, I spent about nine months living and studying in the US each year and three months visiting my wife and daughter in El Salvador. Three months a year with my daughter for five years. That's all I got.
After a few years of community college, I transferred to the University of Houston – Downtown (UHD) and entered the mathematics program. I decided to minor in computer science since I had some programming experience from earlier life interests.
My time at UHD was one of relentless focus. The only upside to being involuntarily separated from your family is that you end up with a lot of free time. I dedicated myself to studying mathematics, and I devoured the curriculum with the appetite of an American pygmy shrew.
I took several mentored studies, which put me one-on-one with professors exploring topics in more depth than a traditional course allows. My favorite was a study in linear algebra, and I credit that course for introducing me to "higher-level mathematics."
I'd always viewed linear algebra as the study of matrices. It's an easy mistake to make, given the typical linear algebra curriculum. But a matrix is just a concrete representation of an abstract mathematical idea. The mechanics of matrices, while important for computation, are just a means to an end. And in all reality, they distract from the beauty of linear algebra.
The mentored study used Sheldon Axler's classic textbook Linear Algebra Done Right — which remains one of my favorite mathematics books to this day — and his manifesto Down With Determinants!, as well as Paul Halmos's Finite Dimensional Vector Spaces.
Reading Axler and Halmos I fielt, for the first time, a textbook speak to me as a future mathematician. Those books are deep. They're technical, for sure, but they also spoke to me on a human level. Axler and Halmos are expert teachers. Their expository style is refreshing, and it planted in me a seed that would grow into a love of writing mathematics.
The UHD mathematics department is small, but it had a massive impact on me. I had the opportunity to take a graph theory course taught using the Moore method. Each week we were given a packet with some definitions of key terms and concepts and a list of twenty or so mathematical statements about graphs. Our task was to determine the truth of as many of the statements as we could. The following week, we took turns presenting our ideas to the class.
Finally, I got to experience what math research felt like first-hand. And I was addicted.
After my graph theory class, I'd pretty much made up my mind that I wanted to be a graph theorist. I approached Dr. Ermalinda DeLaViña, one of the graph theorists at UHD, about working with her for my senior thesis. I also started regularly hanging out in Dr. Ryan Pepper's office, the professor who taught my graph theory course.
My last year at UHD was one full of research. I spent countless hours with Dr. DeLaViña working on conjectures generated by her computer program graffiti.pc. Under her tutelage, I learned the value of using computers to aid mathematical research. I also learned how controversial that topic could be. I wrote and published my first paper with Dr. Pepper.
In the Spring of 2013, I graduated from UHD with a Bachelor of Science in Applied Mathematics and set out to complete a Ph.D. at Texas A&M University.
While I was moving to Bryan, Texas, and preparing to start graduate school, the immigration situation with my wife started moving, too. The five years were up, and Raquel was now eligible to apply for a pardon for the remaining five years of her exile from the US.
At the same time that I was taking graduate-level courses in things like abstract algebra, combinatorics, and differential geometry, I was putting together a statement. I had to prove, with evidence, that I would suffer tangible harm should the government decide to deny Raquel her pardon.
I wrote a comprehensive statement in an effort to give the government zero reasons to deny our family the chance to live together in the US. I think it was around forty pages, including all of the appendices with supporting evidence.
I bound everything together and mailed it to some address in Virginia. Or was it Washington? It doesn't matter. All that matters is that I poured my heart and soul into that statement. And nearly six months later, I received a reply stating that the government had only received a single bank statement from me showing that I sent Raquel some money back in 2008. I had a few weeks to submit the rest of my statement, or we would lose our place in line and potentially the chance for a pardon.
I scrambled to re-assemble and re-submit my statement, absolutely bewildered — although, in hindsight, it's not at all surprising — that the government had managed to lose a forty-page bound document.
Then, the unthinkable happened.
One evening, while visiting my parents in Houston, I got a call from Raquel's cousin. She was in the hospital, and it was serious.
While withdrawing money from an ATM, she was assaulted by someone at knifepoint. They took the $20 or whatever she had and then slit her throat. She was found sometime later by the police.
My entire world caved in.
Suddenly, all that mattered was getting to my wife and getting her and my daughter out of El Salvador.
I filed for a leave of absence at Texas A&M. They agreed to hold my spot in the Ph.D. program for one year. Then I moved to El Salvador and started working on an exit plan.
We couldn't come to the US. Even if we did apply for asylum, the process was so backlogged that it wouldn't provide any immediate relief for our family. And we needed to act fast, especially since my wife had recently given birth to our second daughter.
As citizens of El Salvador, Raquel and our daughter could move freely between the five Central American nations. We decided to move the family to Guatemala.
I needed a job. I reached out to a friend from my days as a live sound engineer. He had opened his own event production company, and I was desperate. It would be tough to work for an event production company in Houston while living in Guatemala, though. But he knew someone that needed a programmer.
I traveled to Houston and met with the owner of an audio/visual installation company. The industry was shifting. The systems they installed were controlled by central servers that processed all the audio and video throughout the facility. He needed someone who could program the servers and build user interfaces for customers to interact with the system from a phone or tablet.
I'd done lots of scientific computing in college, both for mathematics research and while working in a computational chemistry lab (read: a closet with a computer) for a research grant I'd been awarded as an undergraduate.
I had no experience with the kind of programming he needed. But I was desperate, and so was he. Plus, I could work remotely from Guatemala, email files to the company, and support the installation crew via video chat.
We lived in Guatemala for nine months, and our family situation stabilized. I loved getting to spend every day with my wife and daughters. And finally, after a total of seven and a half years of dealing with immigration hurdles, Raquel was issued a pardon.
She applied for permanent residency, and a few months later, we were on our way back to the US.
For almost an entire year, I had done practically no mathematics. There were too many urgent priorities that demanded my attention. But I was anxious to get back to Texas A&M.
There was a problem, though. We were broke. We'd spent all of our money getting the family to safety and paying immigration and legal fees. My parents had helped us cover expenses beyond what we could afford, but even then, we had very little left when we came to the US.
Every Ph.D. student at Texas A&M is supported. Your tuition is paid for, and you're given a monthly stipend for living expenses. And that stipend is enough for a single person living in a tiny apartment or splitting rent with roommates. But my wife and I had to support a family of four.
I had to make a choice.
And I decided that, for the benefit of my children, I needed to leave graduate school and focus on a tech career.
I stand by that decision. But I'd be lying if I told you it didn't leave me feeling hollow.
I've had the privilege of finding moderate success in tech. I never worked at a big tech company, and honestly, I never wanted to. But I found a niche that has afforded me a comfortable lifestyle.
After working for about five years as a programmer, I landed a job at Real Python writing about the Python programming language. The job put me back into the education space, and I've loved it. I get to write and create video courses, and I help other content creators as a technical reviewer.
I even got to write a book, something I'd always wanted to do and something I hope to do again. But the hole that formed in my soul when I left Texas A&M has never been filled.
I don't know if I'll ever go back to graduate school. My oldest daughter is a teenager, and the youngest is halfway between a toddler and a pre-teen. Both are busy with activities: swim team, soccer, gymnastics, violin lessons. Our life is busy, and it's hard enough to find time to write a blog post, let alone finish an advanced degree.
So, I'm still recovering. I might always be recovering.
I might not get the chance to finish my Ph.D., but I've realized that I don't need a doctorate to make an impact in mathematics. All I need is my passion, a drive to continue learning, and a desire to share what I learn with others.
Let's learn some mathematics together. Whaddya say?
]]>