Peter Percival Patterson’s Pet Pig Porky

π! I can rattle off 36 significant figures on demand, but how accurate do you really need to be? The JPL uses 15 digits for interplanetary navigation. What is needed, though?

Let’s start with a 1 m diameter circle, and add successive digits to the approximation. How many digits before you’re being absurd? I’ll give a definition of absurd, too: a difference in the circumference of less than the Planck length, approximately \(\LARGE{1.616229(38)\times10^{-35} {\rm m}}\)

  • For 1 m, the answer is fairly obvious: 36 significant figures
  • For d, 43 significant figures are needed
  • For the distance from the sun to Voyager 1, ~141 AU at time of writing, just 49 significant figures
  • For the Solar System, out to the distance where we believe the Oort cloud ends, ~100000 AU (i.e., the edge of the solar system), 53 significant figures
  • For the size of our observable universe, roughly 93000000000 light years, 63 is enough

What if the Bohr radius (approximate size of a hydrogen atom) was our standard, at \(\LARGE{5.2917721067(12)\times10^{-11}{\rm m}}\)? For the size of the observable universe, we only need 36 figures. Handily, that’s what I know! How much would one need for a circle the size of the orbit of Neptune? 23 digits.

So how accurate is JPL if they were measuring the circumference of a circle at the orbit of Neptune? They could be off by as much as a centimeter! Which, assuming appropriate feedback systems, isn’t going to be anything near significant.

How many do we know? 22459157718361 digits. Why? As George Leigh-Mallory put it, “Because it’s there”.

Who Are You

There seems to be a dearth of articles on setting up full-fledged “automatic” user registration systems for Django 2.0. Yes, there’s documentation, but there’s a lot to wade through, and it’s far from a step-by-step guide, which is what this aspires to be.

The problem for developing many web applications using Django is that adding a user requires logging in through the admin interface to do so. For smaller groups, say, 20 or so, this might be manageable, though annoying. If you develop for deployment to a wide audience, say the entire English speaking world, you don’t want people to email you requests all the time to be added so they can do whatever in your app.

I’ll be assuming that you have a passing familiarity with Django, and have made it through the tutorial and polls app. I’m also assuming that this is among the first things you’re setting up, and you won’t mind blowing away any data you have, because that’s the simplest way to do things in Step 3.

Step 1: Settings

You can customize these as your needs dictate, but here’s a bunch more things to add to your mysite.settings:

LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/accounts/login/'
LOGOUT_REDIRECT_URL = '/'
## Possible email backends. The uncommented one, also the default, is the one we want to use
##   by the end, but the others can be used in development. I find console the simplest for
##   that.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
## Console
#EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
## File
#EMAIL_BACKEND = 'django.core.mail.backends.file.EmailBackend'
#EMAIL_FILE_PATH =  '/tmp/app-messages'
## In Memory (django.core.mail.outbox is a list of EmailMessage instances)
#EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
## Dummy (does nothing--same as /dev/null)
#EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

## All options below are the default values. Unless you run your own SMTP server on localhost
##   with no authentication required (not recommended), you'll probably want to tweak these.
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_HOST_USER = '' #If this is the empty string, no authentication will be attempted
EMAIL_HOST_PASSWORD = ''
## The next two options are mutually exclusive.
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
EMAIL_TIMEOUT = None
EMAIL_SSL_KEYFILE = None #If EMAIL_USE_TLS or EMAIL_USE_SSL are True, specify the path to a
                         #  PEM formatted private key.
EMAIL_SSL_CERTFILE = None #If EMAIL_USE_TLS or EMAIL_USE_SSL are True, specify the path to a
                          #  PEM formatted certificate chain
EMAIL_SUBJECT_PREFIX = '[Django] '
EMAIL_USE_LOCALTIME = False #The default here will timestamp your message with UTC.

Step 2: Some Templates

The next thing we want to do is make a bunch of templates. It’s a tad backward from how Django is normally developed, but trust me. As always, customize as you wish.

In your templates directory, create a “registration” subdirectory. All these templates will live in there.

{% extends "base.html" %}

{% block content %}

{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}

{% if next %}
    {% if user.is_authenticated %}
    <p>Your account doesn't have access to this page. To proceed,
    please login with an account that has access.</p>
    {% else %}
    <p>Please login to see this page.</p>
    {% endif %}
{% endif %}

<form method="post">
{% csrf_token %}
<table>
<tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
</tr>
<tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
</tr>
</table>

<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>

{% endblock %}
{% extends "base.html" %}

{% block content %}

<form method="post">
{% csrf_token %}
<table>
<tr>
    <td>{{ form.email.label_tag }}</td>
    <td>{{ form.email }}</td>
</tr>
</table>

<input type="submit" value="reset" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}
Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
[Django] Password Reset
{% extends "base.html" %}

{% block content %}
<p>You should receieve an email shortly (if an account exists with the e-mail you've
entered) with instructions to reset your password. If you don't receive the email, make sure
you entered the email you registered with, and check your spam folder.</p>
{% endblock content %}
{% extends "base.html" %}

{% block content %}

<form method="post">
{% csrf_token %}
<table>
<tr>
    <td>{{ form.new_password1.label_tag }}</td>
    <td>{{ form.new_password1 }}</td>
</tr>
<tr>
    <td>{{ form.new_password2.label_tag }}</td>
    <td>{{ form.new_password2 }}</td>
</tr>
</table>

<input type="submit" value="reset" />
<input type="hidden" name="next" value="{{ next }}" />
</form>

{% endblock %}
{% extends "base.html" %}

{% block content %}

<p>Your password has been reset. <a href="{% url 'login' %}">Log in?</a></p>

{% endblock %}

Play around with the workflow regarding resetting passwords with your superuser, and make sure all these parts play together. If you want to try the same thing with a “regular” user (there won’t be a visible difference unless you make one in the templates), add one through the admin interface.

Step 3: Altering the User model

Personally, I prefer websites that ask me to log in using my email address. On the wider Internet, it’s guaranteed to be unique so I don’t have to come up with a username like james53401, and I won’t have to remember if I used james53401 on this website or james2557 instead. Yes, I have a password manager that does most of that heavy lifting, but that’s still something that annoys me.

Anyway, the first thing to do would be change the user model to accept an email address instead of a username for login. Easier said than done, though. We get to create a brand new model! You can create an app specifically for the user (./manage.py startapp testuser, which I will assume below) or add the model to an existing app, that’s up to you. Either way, the app will need to be added to the INSTALLED_APPS list in mysite.settings. Also, you’ll need to add this to mysite.settings:

AUTH_USER_MODEL='testuser.MyUser'

Then,

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
import datetime

class MyUserManager(BaseUserManager):
    def create_user(self, email, password=None):
        if not email:
            raise ValueError('Users must have an email address')
        user = self.model(
            email=self.normalize_email(email),
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
    def create_superuser(self, email, password):
        user = self.create_user(
            email,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user
class MyUser(PermissionsMixin,AbstractBaseUser):
    email=models.EmailField(unique=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    USERNAME_FIELD='email'
    objects=MyUserManager()
    def __str__(self):
        return self.email
    def is_staff(self):
        return self.is_admin
    def is_superuser(self):
        return self.is_admin
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField

from testuser.models import MyUser

class UserCreationForm(forms.ModelForm):
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
    class Meta:
        model = MyUser
        fields = ('email',)
    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2
    def save(self, commit=True):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user
class UserChangeForm(forms.ModelForm):
    #this prevents anyone from accidentally screwing up the password hash
    password = ReadOnlyPasswordHashField()
    class Meta:
        model = MyUser
        fields = ('email', 'password', 'is_active', 'is_admin')
    def clean_password(self):
        #this prevents anyone from accidentally screwing up the password hash
        return self.initial["password"]
class UserAdmin(BaseUserAdmin):
    form = UserChangeForm
    add_form = UserCreationForm
    list_display = ('email', 'is_admin')
    list_filter = ('is_admin',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Permissions', {'fields': ('is_admin',)}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()

# Now register the new UserAdmin...
admin.site.register(MyUser, UserAdmin)

Now, we delete everything. If you’re using a MySQL, PostgreSQL, etc. type database, drop all tables. If you’re using sqlite3, delete the database file. Delete all migrations folders from the apps. We “get” to start over.

Now run the following:

./manage.py makemigrations testuser
./manage.py migrate

If you run your server now and go to the login page, there’s still no way to create a user. Let’s fix that.

Step 4: Adding Registration

Add the following to the appropriate views.py:

from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from .admin import UserCreationForm

class UserCreationView(CreateView):
    form_class=UserCreationForm
    template_name='registration/register.html'
    success_url=reverse_lazy('login')

And this to your urls.py:

urlpatterns = [
    path('accounts/register/',views.UserCreationView.as_view(),name='register'),
]

Nearly there!

Step 5: Verifying the Email Address

I’ve also found in my long years trawling the Internet that verifying email addresses is a necessity. I’ve wound up on a mailing list for a travel agency in South Africa, a Botox clinic in Chicago, a Mazda dealership in Texas, and a home remodelling firm in Denver, associated with people named Johann, Joni, Jacci, and others. Instagram was nice enough to send me an e-mail a few dozen times when a Jessica attempted to use my email address to sign up for an account (to the point where I signed up for one just to stop the emails). But to avoid being a bad apple, let’s add in a way to verify the person signed up for an account intentionally, and entered the right email address.

The way we’ll do this is to not allow the user to set a password until they have verified the email address. This sounds a lot like resetting a password, right? So we’ll take advantage of that.

First thing, change UserCreationForm to the following:

class UserCreationForm(forms.ModelForm):
    class Meta:
        model = MyUser
        fields = ('email',)
    def save(self, commit=True):
        user = super().save(commit=False)
        user.set_password(MyUser.objects.make_random_password(48))
        if commit:
            user.save()
        return user

At the moment, it’ll automatically pop the user to the login form, where they can click “Forgot password?” and get a link that way, but we’ll try to streamline that.

Subdivisions

So a Gaussian integer is simply a complex number with real and imaginary parts that are both integers, i.e. \(\LARGE{{\mathbb Z}[i]=\left\{ a + bi\ |\ a,b \in {\mathbb Z}\right\}}\). It has a “norm”, which is the square of its absolute value, so \(\LARGE{\left|a+bi\right|^2=a^2+b^2}\). The “units” of Gaussian integers are \(\LARGE{1, -1, i, -i}\). Nothing new.

I’m trying to understand Gaussian primes, and am finding a dearth of information out there. I find that, for instance, Gaussian primes include the Gaussian units multiplied by primes if and only if it is congruent to \(\LARGE{3\mod4}\). Why? This is not explained. Here’s my investigation into this and other questions.

It seems fairly intuitive that a Gaussian prime can be a prime number multiplied by a Gaussian unit, e.g. \(\LARGE{(2,3,5,7,11…)\times(\pm1,\pm i)}\). But \(\LARGE{2}\) (and, by extension, \(\LARGE{-2}\), \(\LARGE{2i}\), and \(\LARGE{-2i}\), are not. What’s the deal?

Well, a prime cannot be factored in the domain of its definition. Since we’re dealing with Gaussian integers, \(\LARGE{2}\) must have a factorization within those Gaussian integers. What could that  be?Turns out, \(\LARGE{(1+i)(1-i)=1^2+i-i-i^2=1+1=2}\). That’s of course a special case.

What about 5, 13, 17, 29, etc.? There must be factorizations within Gaussian integers for these. Because we’re dealing with complex integers, if you multiply conjugates, you can add a 1 pretty easily. 5 is 4+1, or \(\LARGE{2^2-i^2}\), therefore \(\LARGE{(2+i)(2-i)=2^2+i-i-i^2=4-(-1)=5}\). We have to get slightly more creative with 13: \(\LARGE{(3+2i)(3-2i)=3^2+2i-2i-(2i)^2=9-(-4)=13}\). \(\LARGE{17=4^2+1}\), which, as mentioned, is simple. \(\LARGE{29=5^2+4}\). 37 is easy. 41? Trickier, but we’re still talking sums of squares: 25+16=41. 53=49+4; 61=25+36; 73=9+64; 89=25+64; 97=16+81. Turns out, you can do this for any prime that is congruent to \(\LARGE{1\mod4}\). Fermat even had one of his famous unproven theorems stating so. It was proven as early as 1747. I won’t pretend to understand those proofs yet, but I will accept that this is true. The one that I understand best is the one explained in this video, and was originally stated in a single sentence by Zagier.

But wait! Our factorizations aren’t unique! What if we went with \(\LARGE{(1-2i)(1+2i)=1^2-2i+2i-(2i)^2=1+4=5}\)? This factorization is the same as multiplying each term in the original factorization shown by \(\LARGE{i}\), which, as previously discussed, is a unit. Likewise, one can factor \(\LARGE{6}\) into \(\LARGE{2}\) and \(\LARGE{3}\) or into \(\LARGE{-2}\) and \(\LARGE{-3}\), and these aren’t (typically) considered unique factorizations.

A way to deal with this is to “normalize” one’s choices. The two suggested methods I’ve found are to choose \(\LARGE{p=a+bi}\) such that \(\LARGE{a}\) is always odd and positive and \(\LARGE{b}\) is always even, or to choose \(\LARGE{p}\) such that \(\LARGE{p\mod(2+2i)=i}\).

Even so, coming up with factorizations is cumbersome and perhaps the simplest thing to do is multiply the integers together and find the gaps to get the primes; an analog of the Sieve of Eratosthenes for Gaussian integers. Thus, we have the following:

Units:

  • \(\LARGE{\pm1,\pm i}\)

Primes:

  • \(\LARGE{1+i}\)
  • \(\LARGE{1+2i}\)
  • \(\LARGE{1+4i}\)
  • \(\LARGE{2+i}\)
  • \(\LARGE{2+3i}\)
  • \(\LARGE{2+5i}\)
  • \(\LARGE{3}\)
  • \(\LARGE{3+2i}\)

All the results from multiplying the units by the primes can also be considered prime.

Because of the above, a Gaussian prime \(\LARGE{a+bi}\) fits one of the following scenarios:

  • \(\LARGE{a=0}\), and \(\LARGE{b\equiv3\mod4}\)
  • \(\LARGE{b=0}\), and \(\LARGE{a\equiv3\mod4}\)
  • \(\LARGE{a^2+b^2}\) is prime

See this video for more. I hope he puts together one on Fermat’s theorem on sums of two squares at some point.

Hot Stuff

With Donald Trump officially announcing the US withdrawal from the Paris Climate Agreement, I feel compelled to write a post about climate change. I do find it amusing that the earliest possible effective date of the US withdrawal is a day after the 2020 election, but Donald Trump (presumably) will be president until 20 January 2021.

Anyway, brass tacks.

Anthropogenic Global Warming Is Real

I know few who deny anymore that global warming is happening (people like this guy are obvious exceptions). The possibility was raised in 1824. Humans were producing, in 1896, as much CO2 as natural sources (e.g. volcanoes), but the same scientist who calculated this concluded that humans were, through the burning of coal, a major contributor to climate change, and in the global warming direction.

Numerous scientists began to postulate as to why the temperature of the globe seemed to be increasing, even early in the 20th century. Sunspot cycles were proposed in the 20s, but rejected by most scientists by the 30s. Milanković proposed changes in the Earth’s orbit, but these cycles were determined to be far too slow to be the major contributor to what was observed at the time.

I will note that it wasn’t until the late 50s that a majority of scientists in the field were concerned about potentially “radical” effects on the climate. While in 1968, some remained unsure whether moderate cooling effects of particulate pollution (smog) or the warming effects of CO2 emissions would dominate, a mere seven out of 51 articles published around that time predicted global cooling—the rest sided with the warming hypothesis.

1981 seems to mark the turning point where an overwhelming majority of scientists agreed with the idea that, not only was the globe warming, but that humans were the primary cause of that warming.

ExxonMobil has a nice list of documents and publications from the 70s and 80s indicating that they were/are aware of the warming impact of CO2 release. And here’s 104 more publications they contributed to. Every last one of them says increasing atmospheric carbon dioxide is causing global warming, and that humans burning fossil fuels are causing the increase in atmospheric carbon dioxide (to be fully honest, I have only read a few of these, but it gives me some confidence that I’m not entirely mischaracterizing the tenor of these papers).

I was going to make a nice long essay out of this, but I’ll just stick to saying this: It’s happening, people! Do something about it!

Word Crimes

Microsoft Word has many, many, many failings, but there are usually ways to tame it. Most of them require code. And, as is usual with writing computer programs or scripts, one piece of the puzzle is usually rather picky about one thing or another. In this case, it’s spelling, but I’ll give it a pass for that, and there are some simple workarounds which I’ll leave to the user to figure out.

Today’s conundrum: how to create a sequence, then programmatically create hyperlinked references to that sequence. Let’s pretend you want a sequence that progresses like this (and we’ll keep it simple for now):

  • Foo 00001
  • Foo 00002
  • Foo 00003
  • (etc)

Problem number 1 is creating this in the first place. If you aren’t familiar with the Sequence field code, this will be a basic primer.

In your document, press Ctrl+F9. You’ll see a pair of curly braces and your cursor will be in the middle. Note: you cannot type these curly braces! That won’t work! You must insert them with the Ctrl+F9 hotkey! Next, type SEQ Bar \# “‘Foo ‘00000”

You’ll see something like this:

{ SEQ Bar \# “‘Foo ‘00000” }

If you press Shift+F9, hopefully you’ll see your first number! We’re calling this sequence “Bar”. This is important—more important than the number formatting.

To get the next number in the sequence, there are a few options. The simplest is to copy this text and paste it elsewhere. When you do so, it’ll still say “Foo 00001”; just press F9 to update it, and it’ll refresh to the appropriate number. A second option is to use the Ctrl+F9 hotkey and retype that whole thing again. If you just use

{ SEQ Bar }

You’ll end up with “5” or whatever the sequence number happens to be at that point in the document.

But! We’re not to a programming stage. This is all well and good, and may be sufficient for many applications, but what happens when you want to reference Foo 00532 at some arbitrary location in the document? The standard answer is, create a Bookmark reference for each and every one of these Foo number sequences, each called, say, “Foo 00001”. This gets tedious really fast. When you’re creating hundreds of these, it’s not at all practical.

What you would like to be able to do, step one, is be able to insert a cross reference to this as a numbered item.

If you insert a cross reference (References tab, Captions group, Cross-reference button; Insert, Reference, Cross-reference… for Word versions prior to 2007), though, you won’t find Foo or Bar anywhere! This is a problem!

If you don’t want to do any coding, you can use the “Insert Caption” button (References tab, Caption group; or Insert, Reference, Caption… prior to 2007). In the dialog that appears, Click “New Label…” and enter “Bar”. Then click close. You don’t need to do anything more here. Now go to insert a cross reference (see previous paragraph), and, voila, “Bar” is in the Reference Type drop-down! And all sequence numbers are in the list below!

But we want to make this easy on everyone, so now we must turn to the blessing and bane of every Word expert’s existence: VBA. Step 1: add the caption label. This turns out to be simple:

Application.CaptionLabels.Add "Bar"

And, also, nicely enough, if it already exists, it won’t nag you or call you nasty names. Though, if you want to do it right, you might use one of the following methods:

Function AddCaption(label As String) As String
    Dim labeltext as Variant, i as Long
    For labeltext in Application.CaptionLabels
        If labeltext = label Then
            AddCaption = label
            Exit Function
        End If
    Next
    Application.CaptionLabels.Add label
    AddCaption = label
End Function
Function AddCaption(label As String)
    Dim test As String
    On Error Resume Next
    test = Application.CaptionLabels(label)
    If Err.Number = 5941 Then
        Application.CaptionLabels.Add label
    End If
    On Error GoTo 0
End Function

I prefer the first because it doesn’t rely on VBA’s terrible methods of handling errors.

Anyway, next step! How does one programmatically insert the next item in a sequence into the document? Again, it turns out to be fairly simple:

Selection.Collapse wdCollapseStart
ActiveDocument.Fields.Add selection.Range, wdFieldSequence, "Bar \# ""'Foo '00000""", False

First, I “collapse” the selection to the beginning. This step is optional; if a bunch of text is selected before running this code, this forces the cursor to go to the beginning of that selection instead, so whatever text is selected doesn’t get overwritten. Second, note that in VBA, doubling up the quotation marks actually inserts them into the field. Third, we don’t have to enter “SEQ”, because wdFieldSequence  does that for us.

 

One last step, to make our macro suite complete. Cross references! But we don’t want to have to go through that whole rigamarole of References tab, Captions group, Cross-reference button every time, especially when the cross reference dialog contains hundreds of the things! Type it out! Type “Foo 00532”, highlight it, and run the following macro:

Sub ChangeSelectedTextToLink()
    Dim fld As Field
    Dim findref As String
    findref = Trim(Selection.text)
    For Each fld In ActiveDocument.Fields
    If LCase(Left(fld.Code, 8)) = " seq bar" Then
        If fld.Result.text = Selection Then
            Selection.InsertCrossReference "Bar", wdEntireCaption, Int(Val(Right(fld.Result.text, 4))), True, False
            Exit Sub
        End If
    End If
    MsgBox "Can't find a field numbered '" & findref & "'.", vbInformation
End Sub

Be sure to update the appropriate sections (lines 6 and 8, specifically) when you create your named sequence.

I’m sure that this whole thing can be made more generic, but that’s not something I’m up to doing right now, so I’ll leave that as an exercise to the reader.

Some resources to help you out:

Also (and as a separate note), for those interested in making a custom ribbon, you’ll want a reference that shows you what the imageMso values actually look like, and since Microsoft isn’t nice enough to provide one, I finally found someone who is, here.

Box Set

In Python for now, eventually in VBA, I’m trying to come up with a way to test whether or not a particular quantity is appropriately specified with valid units and tolerances. It turns out to be slightly simpler to be (semi-)lenient than extremely strict, so here’s what I have so far:

#decimal numbers
number='[\d\.]+'
#unicode superscript digits, minus sign, and parentheses
exponent='[\u2070\u2074-\u2079\u207b\u207d\u207e\xb2\xb3\xb9]*'
#SI prefixes
prefix='(?:[YZEPTGMkhdcm\xb5npfazy]|da)?'
#indivisible SI prefixes
ind_prefix='(?:[YZEPTGMkh]|da)?'
#JEDEC/IEC Binary prefixes
bin_prefix='(?:[YZEPTGMK]i)?'
#units that can have a prefix (though some are less standard than others)
prefixed_unit='[ABCFHJKLNRSTVWbglmstu\u2126]|Bq|Ci|Da|Gy|Hz|Np|Pa|Sv|Wb|bar|cd|eV|kat|lm|lx|rad|rem|sr|ua|mol'
#units that cannot have a prefix (normally)
solo_unit='[°\'\"dh\xc5]|°C|ha|mmHg|min'
#units that primarily take binary prefixes
bin_unit='B|bit'
#additional non-standard units that cannot have a prefix (because I said so, not because there's any "ban" on it or anything)
solo_unit+='|°F|ft|in|kt|lb|nmi'
#define a generic unit
unit='(?:(?:'+prefix+prefixed_unit+')|(?:'+solo_unit+')|(?:'+bin_prefix+bin_unit+')|(?:'+ind_prefix+bin_unit'+))'+exponent
#separator: whitespace plus thin space
sep='[\s\u2009]?'
#multiply operator: whitespace, thin space, and cdots
multiply='[×\u2219\u22c5\xb7\s\u2009]'
#define compound units involving only multiplication
compound_unit='(?:'+unit+multiply+'{1,3})*'+unit
#define compound units involving division as well
mega_unit=compound_unit+sep+'(?:\/(?:'+sep+'\('+sep+compound_unit+sep+'\))|(?:\/'+sep+unit+'))*'
#put it all together
everything=number+sep+'('+mega_unit+')'+sep+'(?:±'+sep+number+sep+'\\1|\+'+sep+number+sep+'\\1'+sep+'\/'+sep+'-'+sep+number+sep+'\\1)|[<>\u2264\u2265]'+sep+number+sep+compound_unit
#compile!
re.compile(everything)

This will handle quantities with symmetric and asymmetric tolerances are specified, as well as inequalities with units. It doesn’t enforce asymmetric tolerances (the numbers could, theoretically, be the same), and the separators are lazy for now.

The goal is actually to highlight non-conforming quantities so that they can be corrected. I suspect that will be more than a minor challenge.

mysql to mysqli conversion

If you used an older version of PHP and switched to PHP7, and now all your msyql  commands are failing, you should be able to use the following to fix it. You’ll have to run it in every directory where you have php files, of course.

sed -i 's/mysql_query(\([^,()]*\),\([^,()]*\))/mysqli_query(\2,\1)/g;'\
's/mysql_connect/mysqli_connect/g;'\
's/mysql_select_db(\([^,()]*\),\([^,()]*\))/mysqli_select_db(\2,\1)/g;'\
's/mysql_f/mysqli_f/g;'\
's/mysql_e/mysqli_e/g;'\
's/mysql_numrows/mysqli_num_rows/g;'\
's/mysql_n/mysqli_n/g' *.php

 

vim cheatsheet

As always, this is for my own reference, but hopefully it proves useful to someone out there!

All commands are listed as if they are typed from normal mode; commands that begin with “:” work in ex mode.

commanddescription
:wqsave and quit
:wsave
:qquit (unless there are unsaved changes)
:q!quit without saving
:wasave all
:xasave all and quit
:qaquit all (unless there are unsaved changes)
:qa!quit all without saving
:w {file.txt}save as {file.txt}
/searchfind "search" in current file
:%s/foo/bar/gfind all occurrences of "foo" (in current file) and replace with "bar"
:s/foo/bar/gIfind all occurrences of "foo" (in current line) and replace with "bar", case sensitive
:5,12s/foo/bar/gcfind all occurrences of "foo" (in lines 5-12) and replace with "bar", but ask for confirmation
:s/foo/bar/find first occurrence of "foo" (in current line) and replace with "bar"
:s/foo\c/bar/gfind all occurrences of "foo" (in current line) and replace with "bar", case insensitive
:s/foo\C/bar/gfind all occurrences of "foo" (in current line) and replace with "bar", case sensitive
:%s/Copyright \zs2009\ze J\. Classen/2017/gfind all occurrences of "Copyright 2009 J. Classen" (in current file) and replace only "2009" with "2017"
*find next occurrence of word under cursor
#find previous occurence of word under cursor
%find matching bracket to one under cursor
uundo
<C-R>redo
iinsert at current location
ainsert after current location
Iinsert at start of current line
Ainsert after end of current line
oinsert line below current line and enter insert mode
Oinsert line above current line and enter insert mode
sdelete current character and enter insert mode
Sdelete current line and enter insert mode
cwdelete current word and enter insert mode
ccdelete current line and enter insert mode
Cdelete from cursor to end of line and enter insert mode
.repeat previous operation made in normal mode
rxreplace current character with "x" (do not enter insert mode)
c3bchange previous three words
d4wdelete next four words
dddelete current line
yyyank current line
5dddelete five lines from cursor
5yyyank five lines from cursor
ggmove to first line
Gmove to last line
23Gmove to line 23
zzcenter line vertically in window
hmove left
lmove right
jmove down
kmove up
wmove one word forward
bmove one word backward
8hmove eight lines left
d3kdelete four lines up (three plus current line)
fxfind next "x" in line
Fxfind previous "x" in line
txfind next "x" in line
Txfind after next "x" in line
@:repeat previous operation made in ex mode
Jjoin next line to current line
Ycopy current line
ppaste after current line
Ppaste before current line
Vvisually select current line
v3wvisually select three words
:sp {file.txt}horizontal split with {file.txt}
:vs {file.txt}vertical split with {file.txt}
<C-W>ssplit horizontally (same file in both splits)
<C-W>vsplit vertically (same file in both splits)
<C-W>wswitch to next window
<C-W>Wswitch to previous window
<C-W>nnew window
<C-W>qclose current window
<C-W>oclose all other windows
<C-W>rrotate windows
<C-W>xswap windows
<C-W>Tmove window to new tab
<C-W><decrease window width
<C-W>>increase window width
<C-W>+increase window height
<C-W>-decrease window height
:drop {file.txt}edit {file.txt} in current window; if file is already open, switch to that window instead
:tab drop {file.txt}edit {file.txt} in new tab; if file is already open, switch to that window instead
:tabe {file.txt}edit {file.txt} in new tab
gtgo to next tab
gTgo to previous tab
:tab splitcopy current window to new tab
:tab ballshow all windows in own tab
:cd %:p:hchange to directory of currently open file
maset mark "a" to current line and column (lowercase letters are file-specific, uppercase letters are global)
'amove to line of mark "a"
`amove to line and column of mark "a"
`0jump to position in last file edited

Guelah Papyrus

If you plot

\(\Huge{\frac{1}{2}<\left\lfloor{{\rm mod}\left({\left\lfloor\frac{y}{17}\right\rfloor2^{-17\lfloor{x}\rfloor-\left({\rm mod}(\lfloor{y}\rfloor,17\right)}},2\right)}\right\rfloor}\),

you get

where

\(\LARGE{\begin{alignat}{1} k = 4\ & 858\ 450\ 636\ 189\ 713\ 423\ 582\ 095\ 962\ 494\ 202\\
& 044\ 581\ 400\ 587\ 983\ 244\ 549\ 483\ 093\ 085\ 061\\
& 934\ 704\ 708\ 809\ 928\ 450\ 644\ 769\ 865\ 524\ 364\\
& 849\ 997\ 247\ 024\ 915\ 119\ 110\ 411\ 605\ 739\ 177\\
& 407\ 856\ 919\ 754\ 326\ 571\ 855\ 442\ 057\ 210\ 445\\
& 735\ 883\ 681\ 829\ 823\ 754\ 139\ 634\ 338\ 225\ 199\\
& 452\ 191\ 651\ 284\ 348\ 332\ 905\ 131\ 193\ 199\ 953\\
& 502\ 413\ 758\ 765\ 239\ 264\ 874\ 613\ 394\ 906\ 870\\
& 130\ 562\ 295\ 813\ 219\ 481\ 113\ 685\ 339\ 535\ 565\\
& 290\ 850\ 023\ 875\ 092\ 856\ 892\ 694\ 555\ 974\ 281\\
& 546\ 386\ 510\ 730\ 049\ 106\ 723\ 058\ 933\ 586\ 052\\
& 544\ 096\ 664\ 351\ 265\ 349\ 363\ 643\ 957\ 125\ 565\\
& 695\ 936\ 815\ 184\ 334\ 857\ 605\ 266\ 940\ 161\ 251\\
& 266\ 951\ 421\ 550\ 539\ 554\ 519\ 153\ 785\ 457\ 525\\
& 756\ 590\ 740\ 540\ 157\ 929\ 001\ 765\ 967\ 965\ 480\\
& 064\ 427\ 829\ 131\ 488\ 548\ 259\ 914\ 721\ 248\ 506\\
& 352\ 686\ 630\ 476\ 300\end{alignat}}\)

Also,

\(\Huge{e\approx\left(1+9^{-4^{6\times7}}\right)^{3^{2^{85}}}}\)

It’s accurate to \(\LARGE{\left\lfloor\log_{10}\left(3^{2^{85}}\right)+1\right\rfloor=18\ 457\ 734\ 525\ 360\ 901\ 453\ 873\ 570}\) digits!

Lastly (though I have mentioned this before),

$$\Huge{\sum_{n=1}^{\infty}{n}=1+2+3+4+\cdots=-\frac{1}{12}}$$

as explained here and here and here.

Custard Pie

Happy π Day!

## Pi
##
## Algorithms for calculating the value of Pi
## Written (mostly) on Pi Day 2017

import random
import math
import decimal

precision = 50

D = decimal.Decimal
decimal.setcontext(decimal.Context(prec = precision))

def pi_by_random_numbers(max_number, trials, print_every=10000):
    i = 1
    cp = 0
    while i < trials:
        a, b = random.randint(1, max_number), random.randint(1, max_number)
        if math.gcd(a, b) == 1: # if random numbers are coprime
            cp += 1 # increment coprime counter
        i += 1 #increment loop counter
        if (i % print_every) == 0:
            print((D(6*i)/D(cp)).sqrt())
    return (D(6*i)/D(cp)).sqrt()

def pi_by_riemann_zeta(terms, print_every=10000):
    i = 1
    zsum = D(0)
    while i < terms:
        zsum += D(1)/(D(i)*D(i))
        if (i % print_every) == 0:
            print((zsum*D(6)).sqrt())
        i += 1
    return (zsum*D(6)).sqrt()

def pi_by_bbp(from_digit,to_digit=None):
    if to_digit is None or from_digit<0:
        from_digit,to_digit=0,abs(from_digit)
    if not isinstance(from_digit,int) or from_digit<0:
        from_digit=int(abs(from_digit))
    if not isinstance(to_digit,int) or to_digit<0:
        to_digit=int(abs(to_digit))
    def pi_gen():
        try:
            N = 0
            n, d = 0, 1
            while True:
                xn = (120 * N * N + 151 * N + 47)
                xd = (512 * N * N * N * N + 1024 * N * N * N + 712 * N * N + 194 * N + 15)
                n = (16 * n * xd + xn * d) % (d * xd)
                d *= xd
                yield 16 * n // d
                N += 1
        except KeyboardInterrupt:
            print(N)
            raise
    pi=pi_gen()
    s='3.'
    if from_digit==0:
        print('3.',end='')
    for i in range(from_digit):
        d=next(pi)
    for i in range(from_digit,to_digit):
        d=next(pi)
        print('0123456789abcdef'[d],end='')
        s+='0123456789abcdef'[d]
    print()
    return s