TL;DR Long live mhvx.cc! This is now where my golink instance points to.

My former domain, mhvx.us, has been compromised; it's now registered with this Israeli registrar and redirects to ads.

I will not be registering a .us TLD again. They require your phone number and address, which will be publicly posted on WHOIS and is a pain to get it scrubbed; I still get rings from Indian webdev companies calling to tell me that I own the site and should buy their Wordpress services (to which I refer them to ./211).

Originally, I obtained the domain for free using an MLH promo code from TreeHacks 2022, where it was registered on Porkbun. Trying to transfer to Cloudflare was impossible (whoever manages the .us TLD kept denying my transfer, I don't think this was porkbun) so I let it expire. Little did I know of the eons of grace period days, so I twiddled my thumbs from the time it expired, January 17th, 'til April 3rd when it was snatched up by an ad agency.

]]>
Merry Christmas all. Earlier this winter break I was talking with my pal about why I give a damn
about privacy, why I believe it is crucial to Being. It boils down to this: **the alternative to
privacy is **

The primary ways in which we interact with the world are increasingly dependent on the web — from contacting and maintaining connections with friends, to consuming news and entertainment (more generally, sharing and accessing information).

I hold that we can be thought of as the sum of our actions. This is to say, our digital presence/interactions reflect us; one's online behavior is inextricably linked to one's self.

However, *not* when you are spied on—but simply when you believe you could be under observation—you
behave differently (and importantly this often occurs at a subconsciously level).
So when you're using an online service that's routinely not respected its users' privacy,
your actions are no longer a product of autonomy; rather,
are shaped by the expectations imposed upon you —

Under this environment, the world appears more dim. One becomes less receptive, less imaginative, to the vast possibilities of the world and what it could be; less willing to question as a critic, subjugating oneself to what is; less able to change the mind of yourself and of those around you.

This is why every tyrant has
desired/imposed
surveillance — it squishes dissent (free, possibly opposing thought) before it can form. When you
*won't* do things
(when you self-censor) you become myopic, submitting to that which is set before you.
What limits does society steer you towards or away from?^{1} This answer is only revealed with
hindsight.

This is also why I believe privacy fear mongering—the absolute pessimism perspective—that no matter what
you do, 'they' can comprise anyone^{2}—is wholly unproductive. It is a
self-fulfilling prophecy: if you are sure you are powerless, then you become powerless.^{3} Any
fool can construct a scenario where nothing matters.

^{1} I'll point to gay marriage as stance outside of the realm of public acceptability that is
now widely supported (in the states).

^{2} Privacy pessimist FUD asserts that all hope is
lost, say the feds have a backdoor to the CPU (see, Intel
Management Engine) or have
already cracked modern encryption standards.
I am all for bringing awareness to possible attack vectors or worst-case scenarios, but be realistic.
Delusions and paranoia aren't at all useful.

^{3} Adam Curtis' *Can't Get You Out of My
Head* makes a similar case with respect to conspiracy theories and their ability to erode
hope, one's drive to act.

Say we have a sphere with 10% of it's surface area painted in blue and the remaining 90% red. We want to show that, no matter how the sphere is painted, we can always inscribe a cube inside such that it's 8 corners (vertices) are all touching red.

Formalizing this in terms of probabilities, we operate over the space $\Omega$ of all possible sphere paintings. We know that if we randomly placed a vertex $i$, the chance of it being red is $\mathbb{P}(R_i) = 0.9$ and similarly $\mathbb{P}(B_i) = 0.1$. Thus we can state our problem as: $$ \begin{align} \mathbb{P}\left(\bigcap^8_{i=1} R_i \right) &= 1 - \mathbb{P}\left(\bigcup^8_{i=1} B_i \right) \\ &\geq 1- \sum_{i=1}^8 \mathbb{P}(B_i) \\ &= 1 - 8 \cdot 0.1 \\ &= 0.2 \\ &\neq 0 \iff \bigcap^8_{i=1} R_i \in \Omega \end{align} $$ In other words, we showed that some event (with the property of having all 8 vertices be red) is non-zero. This works because to show that some specific element $\omega \in \Omega$, it is sufficient to show that there exists a probability distribution $p$ over $\Omega$ where $p(\omega) > 0$. This follows from the fact that if $\omega \not\in \Omega$ then $p(\omega) = p(\varnothing) = 0$. This proof technique is called The Probabilistic Method and is notably non-constructive: given the sphere's blue coloring, it does not show how to orient a cube, rather, it shows that there exists some orientation regardless of the exact blue locations (as opposed to a constructive proof, i.e. this hat puzzle

Talking around, Aidan (cowboy hat) came up with another proof. Essentially, each of the eight points correspond to an octant with $100/8 = 12.5\%$ surface area. And as any $c\%$ is painted in any single octant, the other seven will be similarly constrained: there will be a corresponding $c\%$ 'implicitly painted' in all others such that placing a vertex in this area will result in at least one other vertex to be blue. Thus we can view the problem as an issue of covering an octant's $12.5\%$ surface area given $10\%$ blue -- impossible! The proof is a tad handwavy, but gives a sense of how we could collapse the octants to reveal the (minimally $2.5\%$) remaining red area and thus construct a valid orientation.

]]>Scott Shenker gave a talk in my networking class where he shared an anecdote that went something like this:

To give some background, I did my training as a theoretical physicist: I took a postdoc in physics and then I joined Xerox Palo Alto Research Center (PARC). Xerox PARC was basically where Modern PCs and interfaces were invented: it's where Apple.. I don't want to say stole, since Steve Job visited and we showed him all the technology. But that's what the Macintosh was built on, was copying these ideas.

I arrived in 1984 and a year and a half after I joined Don Norman, the lead UI designer at Apple, came to visit Xerox PARC. Now I was a physicist so my ego was not involved in this but certainly the computer scientists thought'we invented this stuff! we know about user interfaces! how could somebody come and tell us, you who built the Mac--this cheap piece of shit--how dare you come and try and tell us how to build user interfaces!'

The auditorium was packed with 300 Engineers, lots of energy in the room. Don Norman, this charismatic man who's even older than I am, just walks up to the podium and looks out at us for for what felt like forever (but it was probably 30 seconds) before asking "How many of you drive a stick shift?" And it's a bunch of male engineers so a bunch of hands went up and you could tell it was 'wellIdrive a stick shift!'

He looks at us and says "None of you shouldEver design a user interface!"

*Mastering Complexity* is valuable: it is crucial when developing novel systems. It is also an indicator of
enjoying the process--learning the skills, nuances, mindset--needed to cope with said complexity.

Driving stick embodies the act of *Mastering Complexity*, and for the sake of itself: the practice is a
feedback loop of constant refinement, sharpening the axe by focusing the subtle nuances. Van argues it is a creative exercise; an
endeavor that (in the states) is the inconvenient, non-default route -- you must yearn to pursue the practice. In
the
same vein, you are "that guy" if you learn drive stick (ala colemake-dh).

These motivations, may be creative in spirit, yet are orthogonal to the goal of design: to extract simplicity;
abolish
complexity. If you are an enjoyer of *Mastering Complexity*, your innate state will blind you from recognizing
the mental grooves you've developed alongside constructing [software, user experience, etc.]. You cannot
succeed in design if you do not consciously curb this ethos.

```
class Counter:
def __init__(self):
self.n = 0
def increment(self):
self.n += 1
def query(self):
return self.n
```

Consider the following trivial counter to the right. It keeps a tally $n$ of how many times
`increment`

has been called, which can then be queried at any time. Under
the hood, since $n$ is an integer it's represented in binary (base-2) which uses $\text{len}(n_2) =
\lceil \log_2 (n + 1) \rceil = \lfloor \log_2 (n) + 1 \rfloor$ bits of memory. Thus this counter has a
space complexity of $\mathcal{O}(\log
(n))$. You can see this in the table below:

$n = n_{10}$ | 0 | 1 | 2 | 3 | 4 | 5 | 8 | 16 | 32 | |
---|---|---|---|---|---|---|---|---|---|---|

$n_2$ | 0 | 1 | 10 | 11 | 100 | 101 | 1000 | 10000 | 100000 | |

$\lfloor \log_2 (n) + 1 \rfloor$ | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 5 | 6 |

Can we do better? That is, can we come up with a better representation (data structure)? Lets say there does in fact exist some schema such that we can represent a $n$umber using less than $\log(n)$ bits. Then, by the Pigeon Hole Principle there exist two distinct integers $a,\ b \in [n]$ corresponding to two different iterations of increments which map to the same representation in memory. Therefore, when queried they both return the same value which is a contradiction! So at least until Quantum Computers aren't a meme, we can't do better.

This lead to an interesting insight: what if we could be 'okay' with $a,\ b$ mapping to the same memory? This would involve losing some information, so we'd only have approximations the true values of $a$ and $b$ represent. Why and when would we want to do this? In 1985 Robert Morris was working at Bell Labs on the Unix spellchecking program and wanted to keep track of trigram counts, requiring $26^3$ simultaneous counters. To reduce the memory needed, he invented the Morris Counter which approximates this count proportional to $\mathcal{O}(\log (\log (n)))$ space. You can read much more of the history here. And even with the cheap and bountiful memory we have now, Redis implements an altered Morris counter in it's key eviction policies' Least Frequently Used algorithm: this requires keeping track how many times each key in the database has been queried to prune the least used.

```
class MorrisCounter:
def __init__(self):
self.X = 0
def increment(self):
if random.random() < 1/(2**self.X):
self.X +=1
def query(self):
return (2**self.X) - 1
```

The Morris counter itself works by incrementing the counter probabilistically. That is, after `increment`

is called $n$ times then our representation $X_n$ is increment
with a probability of $\frac 1 2^{X_n}$ and `query`

returns the estimate
$\hat n = 2^{X_n} - 1$. Notice how this estimate can only take on a subset of values-- this is the
tradeoff that allows us to obtain better memory complexity. Intuitively, the chance we increment $X_n$
decreases logarithmically with $n$ so we can speculate that our estimate has space complexity of
$\mathcal{O}(\log (\log (n)))$. To prove that that
our $\hat n$ is given by the prior formula is accurate, we can prove with induction that $\Bbb{E}
\left(2^{X_n}\right) = n+1$:
$$\begin{align*}
\mathbb{E}\Big(2^{X_{n+1}}\Big) &= \sum_{i=0}^\infty \Bbb{P} (X_n = i) \cdot \Bbb{E} \Big( 2^{X_{n + 1}}
\mid X_n = i\Big) \\
&= \sum_{i=0}^\infty \Bbb{P} (X_n = i) \cdot \left(\left(1- \frac 1 2^i \right) 2^i + \frac 1 2^i 2^{i+1}
\right) \\
&= \sum_{i=0}^\infty \Bbb{P} (X_n = i) \cdot \left((2^i - 1) + 2\right) \\
&= \sum_{i=0}^\infty 1 \cdot \Bbb{P} (X_n = i) + \sum_{i=0}^\infty 2^i \cdot \Bbb{P} (X_n = i) \\
&= 1 + \Bbb{E} \Big(2^{X_n}\Big) \\
&= n + 2 \\
\end{align*}$$
The second term expands to the expected value if incremented plus the expected value if not
incremented. And after expanding, the first term simplifies to $1$ given by
law of total probability and the
second is the expected value of $X_n$.
It can also be shown by a similar process that the variance is given by $\text{Var}(\hat n) = \frac{n^2}{2} - \frac n 2 - 1 < \frac{n^2}{2}$. With this, we can use Chebyshev's inequality to set up an equation to bind the variance: $$\begin{align*} \mathbb{P}(|\hat n - n| \geq \varepsilon n) & \leq \frac{\text{Var}(\hat n)}{\varepsilon^2 n^2} \leq \delta \\ & < \frac{\frac{n^2}{2}}{\varepsilon^2 n^2} = \frac{1}{2\varepsilon^2} \leq \delta \\ \end{align*}$$

Instead of incrementing with probability $\frac 1 2 ^X$, we use $\frac 1 {1 + \alpha}^X$, where alpha is some constant ($\alpha = 1$ in the case of OG Morris Counter). We also have return our estimate $\frac {(1 + \alpha)^X}{\alpha} - 1$. This now gives us control over the behavior of our counter: intuitively, as $\alpha \to 1$ we approach a deterministic counter so $\alpha$ is directly related to memory usage (bits) and inversely related to variability. In his original paper, Morris showed that if we set $\alpha = \varepsilon^2 \delta$ leads to a $(1+\varepsilon)$-approximation with $1-\delta$ probability using $\mathcal{O}\left(\log \log n + \log \frac 1 \varepsilon + \log \frac 1 \delta\right)$ bits. Then, in 2022 (40+ years later!) Jelani Nelson and Huacheng Yu proved that there exists an even tighter bound of $\mathcal{\Theta}(\log \log n + \log \frac 1 \varepsilon + \log \log \frac 1 \delta)$.

Another improvement we could implement is to use a deterministic counter for small
values and switch over to approximate after a certain threshold `lim`

:

```
class MorrisAlpha:
def __init__(self, a=.05, lim=8):
self.X = 0
self.a = a
self.lim = lim
def increment(self):
if self.X < self.lim:
self.X += 1
elif random.random() < 1/((1 + self.a)**self.X):
self.X += 1
def query(self):
if self.X <= self.lim:
return self.X
return ((1 + self.a)**self.X)/self.a - 1
```

What if we used many concurrent, independent Morris counters? That is, we have $y$ counters $\{\hat n_1 \dots \hat n_y\}$ and we returned the average of their outputs when queried: $\hat n = \frac 1 y \sum_{i=1}^y \hat n_i$. The expectation of a summation of independent random variables is given by $\Bbb{E}(\hat n) = \Bbb{E}\left(\frac 1 y \sum_{i=1}^y \hat n_i\right) = \Bbb{E}(\hat n_i) = n$. When a random variable is multiped by a constant $\gamma = \frac 1 y$ then it's variance changes by $\gamma^2$: $\text{Var}(\hat n) = \text{Var}\left(\frac 1 y \sum_{i=1}^y \hat n_i\right) = \frac {1}{y^2} \sum_{i=1}^y \text{Var}\left(\hat n_i\right) < \frac{n^2}{2y}$. We can then set up Chebyshev's inequality to get the following bound: $$\Bbb{P}(|\hat n - n| \geq \varepsilon n) \leq \frac{1}{2 \varepsilon^2 y} < \delta$$ for $y = \left\lceil \dfrac {1}{2 \varepsilon^2 \delta}\right\rceil$. This has space complexity of $\mathcal{O} (y \cdot \log \log n) = \mathcal{O} \left(\dfrac {\log \log n}{\varepsilon^2 \delta}\right)$.

The main takeaway is that we can now choose how tight we want the variance to be by averaging many copies of Morris, but at the cost of blowing up our memory.

```
class MorrisConcurrent:
def __init__(self, y):
self.X = 0
self.ys = [MorrisCounter() for _ in range(y)]
def increment(self):
[y.increment() for y in self.ys]
def query(self):
return sum([y.query() for y in self.ys])/len(self.ys)
```

We can go even further by using another $z$ independent instances of the concurrent counters, each with chance of failure $\delta = \frac 1 3$. Then, we output the median of all $y$ counters where the median is valid iff we have $\frac z 2$ 'successful counters'. Since $\delta = \frac 1 3$ then a deviation of $\frac z 2 - \frac z 3 = \frac z 6$ from the median is valid. We can then set up a Hoeffding bound: $$ \Bbb{P}\left(\sum_{i=1}^z Y_i \leq \frac z 2\right) < \Bbb{P}\left( \left| \sum_{i=1}^z Y_i - \Bbb{E}(Y_i) \right| < \frac z 6 \right) \leq e^{-2 (\frac{1}{6})^2 z} \leq \delta %> $$ Thus, we have a total of $yz = \left\lceil\dfrac {\log{\frac 1 \delta}}{\varepsilon^2}\right\rceil$ copies of the original Morris counter. Once any of the counters reach $\log \frac {yzn} \delta$ the chance that it increments again is $\frac \delta {yzn}$ so the chance it increments in the next $n$ increments is $\frac \delta {yz}$. Thus, if we set the bound $\delta$ such that none of the counters go above $\log \frac {yzn} \delta$ (using $\mathcal{O} \left({\log{\log \frac {yzn}\delta}}\right)$ bits) then we have a bounded space complexity of $\mathcal{O} (yz \cdot \log\log n) = \mathcal{O} \left(\dfrac{\log \frac 1 \delta \log\log n}{\varepsilon^2}\right)$.

You can see that the subsequent two algorithms are actually slower than a Generalized Morris counter with optimal $\alpha$ that Morris proved in his original paper. However, this process of taking the median of many means (of a fixed failure rate) exponentially decreases the overall failure rate and can be applied to other algorithms to obtain a better space complexity.

$$x_1 + x_2 + x_3 + x_4 + x_5 = 50$$
$50$ balls, $5$ bins: ${50 + 5 - 1 \choose 5 - 1}$
$$x_1 + x_2 + x_3 + x_4 + x_5 \leq 50$$
$\sum_1^5 x_i = n$ with excess of $50-n$ which we can place in an extra bin: $50$ balls, $6$ bins:
${50
+ 6
- 1 \choose 6 - 1}$
$$x_1 + x_2 + x_3 + x_4 + x_5 = 50$$
$$\text{st } (\forall i \in [5])(x_i \geq 1)$$
Distribute $5$ beforehand, leaving $45$ balls still to be distributed: ${45 + 5 - 1
\choose 5 - 1}$
$$x_1 + x_2 + x_3 + x_4 + x_5 = 50$$
$$\text{st } (\forall i \in [5])(x_i \geq i)$$
Distribute $1,\dots,5$ beforehand, leaving $50-\sum_1^5i = 35$ balls still to be distributed: ${35 +
5 -
1
\choose 5 - 1}$
$$x_1 + x_2 + x_3 + x_4 + x_5 = 50$$
$$\text{st } (\exists \gamma \in [50])(x_1 \leq \gamma)$$
This is the very first case, excluding those when $x_1 > \gamma$. That is, when we have $50 -
\gamma$
left to distribute. Thus we can take our first case and pattern-match with the prior case: ${50 + 5
- 1 \choose 5 - 1} - {(50-\gamma) + 5 - 1 \choose 5 - 1}$

Huh? See Balls into bins problem.

]]>I have now typed in Colemak for 391 days in a row, so I feel somewhat qualified to tell you why you should consider (not) switching your keyboard layout.

Colemak is a modern alternative to the QWERTY and Dvorak layouts, designed for efficient and ergonomic touch typing in English. Learning Colemak is a one-time investment that will allow you to enjoy faster and pain-free typing for the rest of your life (colemak.com)

Simply, Colemak places the most commonly typed letters (based on the English language) on the home row and determines the position of the rest by accounting for finger distance, row jumping, same-finger typing etc, etc.

Switching to Colemak takes long. And is just hard. You have to break your old muscle memory. Typing is going to be a slow, ugly drudge for a while. I got back to averaging 70 WPM (my OG QWERTY speed) after a (long) month. Then again, my roommate Jack got back 84 WPM after two days (he was formerly avg 140)-- this is a developing situation (he just got 84 five minutes ago) but at this rate I'm sure he'll get 140 by the end of the week. This is the hardest part of the whole process.

After you learn Colemak, good luck using other people's computers. It's embarrassing. When you learn Colemak, you have to destroy all your pre-existing typing habits, thus, you (need to) forget QWERTY (on a regular keyboard, phone fine). I now carry around a USB with Windows/Linux Colemak files (which isn't terrible since I always have a password-backup USB on me). Regardless, it's a hassle. Additionally, sometimes you do not have permissions to install layouts (school/works computers). To circumvent this, I was looking into building a physical middle-man between the keyboard and computer using an Arduino. The parts arrived but now that I'm at Berkeley and I have no free time (or, there are more fulfilling things to do with my free time). Maybe this summer. Anyways, more reasons not to use Colemak:

If you use VeraCrypt or any other encryption system that requires unlocking the drive prior to the operating system loading (so, yes, LUKS) then typing your password is pain: your keyboard will be typing in QWERTY (unless you have a hardware Colemak keyboard, which you probably shouldn't). You can circumvent this by setting your keyboard layout temporarily to QWERTY, then typing your password like you normally would (just type what you would by pressing the keys as if they were Colemak), and you're set. Additionally, some (most) of Linux DE's do not load the layout on the initial lock screen so you'll have to slowly type it out. I've found that after a while my brain can store the QWERTY-muscle memory of my most common passwords so it's not terrible after you encode the important/common ones in your brain.

Vim is not as slick: your movement keys aren't in the
same proximity. You can rebind the keys, of course, but
out of the box (ex. when you SSH into a machine) it's
annoying. More generally, shortcuts may be more a hassle
for other programs. For what it's worth, Colemak does
have `C`

and `V`

unchanged (for
copy / paste respectively) unlike Dvorak.

Finally, I can only imagine it's fairly easy to fingerprint Colemak (or really any non-mainstream layout) users, either through reading the current keyboard layout or by unique, colemak-specific typing mannerisms (look up 'Behavioral Biometrics'). This only matters if you're a privacy nut or Snowden-level wanted.

Summary: the hardest part is the first month (getting back to your old WPM). The rest of the reasons are mostly small nuisances and edge cases that you can judge if they matter.

It's fun to learn as a challenge. If you like learning
skills, this is one of the ones to at least try out. I
can tell you it's more useful than knowing morse-code
(besides perhaps `...---...`

) and around the
same impressiveness as being able to solve a rubik's
cube. It's also fun to type: if you go online you'll see
people talking about the sweet 'finger rolls' that Colemak is conductive to. If you switch, type 'tenderheartednesses' and you'll see.

You will probably get faster. Then again, I may have gotten equally better had I spend this time and focus towards improving regular typing. Anyways, I went from averaging 70 to 100 WPM. Below is my progress on monkeytype:

Also, you may not get RSI or carpal tunnel which is pretty dope 👌.

To end this post, I'm actually a liar! I use a derivative of Colemak, called Colemak-DH, which has a few other additional tweaks you can read about on the website. Skim it and choose if you want it or Colemak—— it really doesn't matter all that much. One minor thing is that base Colemak is included Manjaro (and presumably other distros) while -DH is not (making set up a bit more painful.)

When you do decide to take the Colemak pill, set aside a weekend where you are okay being unproductive while using a computer. To learn Colemak, you have to use it lots. You can't swap back between it and QWERTY, so you have to grind back to an acceptable WPM.

After running the installer (colemak installers, -DH installers), I started by going through all the keys on keyzen colemak, then colemak.academy, and finally grinding monkeytype. This is what I recommend to others, and how my roommate Jack is learning very quickly. You may also want to checkout the subreddit to see what's worked for others. Ultimately, it boils down to focusing on breaking your old habits. If you think that sounds lke a fun challenge, highly recommended!

I received an email threatening legal action against me:

After this blog post goes up, I am taking down
pass.mehvix.com. But, given the nature of open source code,
**I have no power over anyone forking and running their own instance
**. Or using `about://inspect/#devices`

in a Chromium browser to see the app's source code.
Code is free speech.
You can't stop the signal.

It was a good run- I started pass for myself and roomies, then they told five people, and those people told five more, etc etc until it came full loop where people would tell me about pass.mehvix.com not knowing that I was mehvix.

Here's the cloudflare analytics for anyone interested:

Try and see if you can infer when lunch and dinner are based on the spikes. Also, don't worry if you've used pass and are concerned about your privacy: I'm a privacy nut and have never collected any user information.

]]>
This past weekend I was walking around downtown Berkeley
with my friends to get
*Kingpin Donuts*
when I thought of this contradiction involving the quickest
way to traverses a square with infinitely small street
blocks street blocks.

Consider a `1 x 1`

unit square and a particle
starting at the bottom left of the square:
`(0,0)`

. This particle wants to move to
`(1,1)`

[top right] as quickly as possible while moving only along
the edges [streets] of the square. In the
`1 x 1`

case, you have two paths each involving
two steps: (1) up, right and (2) right, up. Since these
paths are symmetric, we'll treat them as a single path. In
this single path, we clearly travel a distance of
`2 units`

[`1 unit per step`

and
`2 steps`

].

Now, what if we divide our existing block into forths; that
is, we have four `0.5 x 0.5`

unit squares. Now,
consider the following two paths that each take four steps:
(1) right, right, up, up and (2) right, up, right, up. Just
as the previous `1 x 1`

case, both of these paths
travel a total distance of `2 units`

[each of the
four steps travels `0.5 units`

].

Say we repeat this dividing of the square
`n`

times so our original `1 x 1`

unit
block is split into `2^n x 2^n`

sub-blocks, each
with side length `1/2^n`

. Now both the outermost
and alternating paths each take `2 * 2^n`

steps:
(1) right `2^n`

times followed by up
`2^n`

more times and (2) right, up
`2^n`

times. Again, both travel a total distance
of `2 units`

for all `n > 0`

.

However, as we take `n`

to infinity the
alternating path looks more and more like particle is
travelling along the hypotenuse line from
`(0,0)`

to `(1,1)`

. But, we know that
the distance traveled is always `2 units`

. How
can this be if the particle is travelling along the distance
of the hypotenuse which has a distances of
`√2 units`

?!

This contradiction stumped me for a bit, but I thought of three different ways to explain this seeming contradiction while showering last night.

We know that the particle takes a total of
`2 * n^2`

steps, each of which are at a constant
speed [lets say `1 unit/second`

]. In the original
`1 x 1`

case, we travel `2 units `

so
we take `2 seconds`

. In each following case, we
take just as long since both the speed and distance are
constant so we know that the time we travel is constantly
`2 seconds`

. Therefore, in the
`2^n x 2^n`

sub-blocks case, since we know that
we're traveling at a constant `1 unit/sec`

and
taking `2 seconds`

meaning we're traveling
`2 units`

.

If we watched this scenario play out, we would see the
particle appearing to move at a speed of
`1/√2 units/sec`

. That is, even though the line
*looks* like the hypotenuse, it isn't. This makes sense
because we cannot travel along the hypotenuse, no matter how
small we get: At the very beginning we said the particle can
only move along the edges [streets] of the square, meaning
only travel right and up. So when we see the particle travel
along the hypotenuse at `1/√2 units/sec`

, we're
actually seeing the particle move right and up [or up and
right] a tiny amount that we only *assume* to be along
the hypotenuse.

Again, the time staying constant because our speed and
distance stay the same: we keep moving
`1 unit/sec`

and we travel a distance [2 for
up/right] * [paths] * [distance/path] =
`2 * 2^n * 1/2^n = 2`

so we take
`2 seconds`

to traverse the grid even as we push
`n`

to infinity.

Instead of dividing the square into sub-squares, what if we
tiled instead so that we doubled each existing square. That
is, we'd start with a single `1 x 1`

unit block,
then grow to `2 x 2, 4 x 4, 8 x 8, ...`

etc.
However, while we exponentially increase perimeter, we'll
also increased our speed proportionally [starting at
`1`

unit/sec, then
`2, 4, 8, ...`

units/sec]. That way, the particle
will always take 2 seconds to complete the square.

In addition to seeing that this 2 seconds is a constant for
any resolution, we can also see that the two steps of both
right and up movements are finite distances. That means that
both the up and right movements are not, and cannot ever, be
treated as a single move diagonally [this is what we
implicitly assumed was happening when the sub-square sides
were of length
`1/2^n x 1/2^n`

].

Adding on to the explanation involving tiling above, we can
write an equation to relate the total distance traveled
[units] per speed [in units/sec]: `2n/n`

. With
this, we can take the limit of `n`

to infinity
and clearly this ends up as `2`

. This result has
the units `seconds`

which is consistent with our
other for the particle to travel all paths.

With that being said, this seems to say that travelling eight blocks east and eight blocks north should take the same time as travelling east then north eight times (eight is arbitrary).

In the real world, this isn't often true^{1}. Blocks aren't perfect rectangles made from streets and
your steps are rarely parallel/orthogonal to the x/y axis
so, unlike the idealized example, you're rarely moving in
just the x or y direction at a single time. Regardless, I
still think it's an interesting thought puzzle of sorts.

Shout out to David Jiang + Jack Guerrisi for helping me wrap my head around this problem and to Frank Liu and Phoebe Chen for listening to me try to explain this while toasted on the way to Asian Ghetto! :)

---

^{1}
*Update #1:* I found a set of stairs outside the
MSRI
(fitting!) which is a sort of tiling switchback in that it
constrains you to moving like the particle in this problem.
It was very foggy that night, so here's a scuffed photo:

---

*Update #2:* Aidan McNabb (cowboy hat) showed me a
contradiction that pi equals four (the
staircase paradox) which follows a similar line of reasoning (has the same
faults). Turns out this is a fairly old problem and there's
a entire form of geometry called
Taxicab geometry
.

Hey math monke(s), do you use RSS (bonus if you are cool enough to have a cloud.mehvix.com account and use the News app) and read AoPS blogs? If so, you should checkout aops-rss.mehvix.com — plug in a blog's URL like Dan's (kiss_mm) and it should spit out a RSS feed. Prior to this project, I haven't used Ruby so there may be kinks and edge case errors — if you run into any, create an issue on Github or reach out :)

If you haven't heard of RSS, you should check it out. In essence, it lets you keep up with online feeds (blogs / forums posts, news sites, podcasts, Calvin and Hobbes, etc.) without having to manually check webpages for updates. I'd recommend using this extension for Chrome-based browsers to see if a site has an RSS feed. Plus, with Nitter and Bibliogram you can create proxied RSS feeds for Twitter and Instagram accounts so you don't have to deal with the bullshit spying + ads. If you don't have a cloud.mehvix.com account, there's lots of RSS Readers out there. I've heard alright things about Feedly and NewsBlur but do your own due diligence.

]]>
I have a *yuuuug* bookmarks folder of assorted articles
I've come across but haven't had the time to delve into. Now that I'm through my UW classwork I've finally been able to chunk through some of them and today and I came across the (mostly-irrelevant, website-related decision) debate between no-www and **www**.yes-www.

The argument by no-www is that `www`

is supposedly “deprecated” in that it's redundant to type. But, if you look at our tech overlords like Google, Amazon, Apple, etc. you'll see that they redirect no-www to www (more on why later). For what it's worth though, having no `www`

has its *minimalism aesthetic* and anecdotally most smaller sites seem to have no www.

yes-www has actual technical arguments: (1) you can't use a CNAME record on a "naked domain" (that is, sites with no `www`

) and (2) cookies on the naked domain get sent to all subdomains (so all `*.mehvix.com`

domains) which can mess up caching for static files.

Both of the pro-`www`

points aren't an issue for me now, but best not to limit myself later down the line. To swap, I prefaced the domain with `www`

on this site's CNAME on the GitHub, changed my `CNAME`

and `A`

DNS records so that all prior non-`www`

links would go to the new `www`

version.

Anyone who has physical access to your Windows machine can plug in and boot to a live USB drive which allows them to see/edit/copy your entire filesystem — all images, documents, applications, etc. — without logging in. All this can be prevented by just encrypting your file system.

Microsoft doesn't value your security. Their proprietary encryption software BitLocker is only available for paying Windows Pro and Enterprise users (and even then, it isn't enabled by default 🤔). And, if for some reason you're paying (extra) for Windows, you still shouldn't use BitLocker. Microsoft practice security through obscurity: like Windows, BitLocker code is proprietary meaning there's no way to vet it's security or verify there are not backdoors for any of the alphabet boys
which Microsoft has done before — from what's been made *public*, Microsoft has helped the NSA to circumvent their own encryption, backdoored pre-installed disk encryption, informed the NSA of bugs in Windows before fixing them, and there's a secret NSA key in Windows, whose functions we don't know.

The alternative to proprietary software is open source software: anyone can verify an open source project's legitimacy and contribute improvements of their own. For Windows, the open source encryption software I'd recommend is VeraCrypt.

You can download the latest installer of VeraCrypt here. Once you install it, the process of encrypting your drive(s) is fairly trivial. To encrypt the storage device with the operating system, you start by going selecting `System`

> `Encrypt System Partition/Drive...`

and following the creation wizard. The default AES + SHA-512 is more than enough. Note that if you are dual-booting from *different* disks you do not need to use the dual-boot setting. Once you get to "Collecting Random Data", shake your mouse around — VeraCrypt gathers random data from your mouse's position because (somewhat unintuitively) it's hard for computers to generate unpredictable random numbers on their own.

Later, when prompted to, I'd recommend that you create a rescue disk — this is allows you to unlock your drive if one day Windows can't boot or the VeraCrypt bootloader get damaged (unlikely, but possible). All you have to do is grab a USB drive, create a folder called `EFI`

, and add copy the generated Rescue Disk. If something ever does go wrong and you want to decrypt your drive outside of Windows, all you have to do is plug in and boot to the USB then enter your decryption password. Because you still need to have your password, you don't need to keep this USB especially secure (but hey, it can't hurt!).

You'll also be able to optionally write wipe the drive of all unencrypted data from the drive. Multiple passes is an urban legend (doesn't improve security), wears the drives down, and can take a very long time. One pass is sufficient and doesn't take too long (compared to no passes).

After selecting the number of passes, you should be prompted to test that the boot loader works before VeraCrypt proceeds with encryption and the wipe(s). Once you restart, you'll be be prompted with the VeraCrypt boot loader before the Windows login screen. Here, you enter your password (and pin if you opted for one; if you didn't leave it empty). Once you get back into Windows, VeraCrypt will open and prompt you to actually encrypt your data and fulfill the wipe(s). When that completes, you're done!

]]>This blog goes over how to install CalyxOS — a custom Android ROM for (somewhat ironically) Google's Pixel phones. It's built with "Privacy by Design"; minimizing tracking done by phone manufacturers, cell providers, ISPs, advertising companies, etc. That being said, it's not as extreme as alternatives, notably GrapheneOS, and seeks to maintain a balance between usability and privacy. You can check (and contribute) app compatibility here.

---You will loose all of your data so backup stuff you want to keep. You won't be using Google to backup/restore once you get CalyxOS installed, so you're best bet is probably to transfer files directly to your computer. You can do so by enabling "Data Transfer through USB" so your files can be viewed & copied from your PC File Explorer.

Go into your device's settings, `About phone`

,
and down to `Build number`

. Spam the button 10
times until you get developer. Then, go back and to
`System`

> `Advanced`

>
`Developer options`

and enable
`USB debugging`

and
`OEM unlocking`

Download the
Google USB Drivers
and extract the files somewhere on your PC. Then, showdown
the phone, press and hold the power and volume down until
the bootloader (black screen with a red triangle) appears.
You can then connect the phone to your PC if you haven't
already and go to Device Manager in Windows. Then, navigate
to `Other Devices`

, find your phone, right-click,
and go to `Update drivers`

>
`Browse...`

> point to where you extract the USB
drivers, and Windows should do the rest.

Create a new folder and download the
CalyxOS release for your device
(don't unzip it though!) as well as the
device flasher
(which may be falsely flagged as a virus). From there, you
can run the
`device-flasher.exe`

and follow along the
instructions. If you've followed the steps this far, the
only step not done is to remove the SIM card. Once you hit
enter to begin the flash, you can just sit back and wait for
it to install. Once it has, all you have to do is re-lock
the bootloader and you're set!

If you need help with CalyxOS I'd recommend asking any through any of the following services:

- Matrix: #CalyxOS:matrix.org
- IRC: #calyxos on Freenode
- Telegram: CalyxOSpublic
- Reddit: /r/CalyxOS

Last, there's an blog for updates + known bugs (and fixes) that's got an RSS feed too.

]]>
Upgrading to Nextcloud 18 broke both importing public Google
calendars and external calendars so that they don’t update
automatically. Fortunately, both of these issues have
workarounds

For external calendars, Nextcloud needs a
`.ics`

or `.ical`

file. To add a
Google Calendar, we just have to adjust the URL's formatting
which you can do automatically using
this online tool. Alternatively, you can follow the manual method below.

For embedded calendars (in this example the
Codeforces calendar)
you can find the `cid`

by clicking on the plus
button in the bottom right corner which will open the
following URL:

`https://calendar.google.com/calendar/render?cid=k23j233gtcvau7a8ulk2p360m4@group.calendar.google.com&ctz=America/Chicago`

Alternatively, you can get also use inspect element which
will yield the following URL containing the
`src`

:

`https://calendar.google.com/calendar/embed?src=k23j233gtcvau7a8ulk2p360m4@group.calendar.google.com&ctz=America/Chicago&hl=en&wkst=1`

Then take everything **after**
`src=`

, `cid=`

, or
`/ical/`

and up to the next `&`

sign,
oftentimes this will look something like
`[id]@[group].calendar.google.com`

. Then, take
this string and add it to the following:

`https://calendar.google.com/calendar/ical/[add here!]/public/basic.ics`

For example, using the Codeforces calendar you'd grab
`k23j233gtcvau7a8ulk2p360m4@group.calendar.google.com`

and get the following:

`https://calendar.google.com/calendar/ical/k23j233gtcvau7a8ulk2p360m4@group.calendar.google.com/public/basic.ics`

You can then use this URL to as the external URL in the Nextcloud Calendar app.

The default subscription refreshing interval is of one week (unless the subscription itself asks for a different rate).

You may override it with something like:

```
sudo -u www-data php /var/www/html/occ config:app:set dav
calendarSubscriptionRefreshRate --value "P1DT6H"
```

which would set the interval to 1 day and 6 hours. The
`--value`

data type is
DataInterval.

Nextcloud is a FOSS solution for cloud storage, among other capabilities such as email, notes, calender, tasks. Pretty much every thing that you can do with the Google suite, you can use Nextcloud for. For that reasoning,

We'll start by getting the machine up-to-date and setting up our webserver, apache, with the following commands

```
$ sudo apt update && sudo apt upgrade
$ sudo apt install apache2
$ systemctl start apache2 # turns on apache now
$ systemctl enable apache2 # auto-start apache2 on boot
```

If you’ve configured the server right, you should be able to
go to the server’s IP (which you can find by running
`hostname -I`

) in the browser of your choice and
see the "Apache2 Ubuntu Default Page."

Next, we can install MariaDB — the database.

```
$ sudo apt install mariadb-server
$ mysql_secure_installation
```

During the installation you'll be prompted to login (but we
don't have an account setup yet!). Just hit enter and it'll
prompt you to create an account. After that, accept all of
the recommended prompt options and you’re set. After we've
installed the database, we'll then configure it for
NextCloud. (note that "`Password`

" will be your
own password created during the installation phase).

```
$ mariadb
CREATE DATABASE nextcloud;
CREATE USER nextcloud IDENTIFIED BY 'Password';
GRANT USAGE ON *.* TO nextcloud@localhost IDENTIFIED BY 'Password';
GRANT ALL privileges ON nextcloud.* TO nextcloud@localhost;
FLUSH PRIVILEGES;
quit;
```

While it's not necessary for NextCloud, we'll install phpMyAdmin so that we have a GUI interface to our database.

```
$ sudo apt install php libapache2-mod-php php-mysql
$ sudo apt install phpmyadmin
```

When running through the installation dialog, make sure to
set the webserver as `apache2`

and follow all of
the default options. After the install, we will symlink the
apache and phpMyAdmin configurations which, in tandem with
the following commands, will enable phpMyAdmin.

```
$ sudo ln -s /etc/phpmyadmin/apache.conf /etc/apache2/conf-available/phpmyadmin.conf
$ a2enconf phpmyadmin
$ service apache2 reload
$ systemctl reload apache2
```

Now, (fingers crossed) if you go to
`[server ip]/phpmyadmin/`

you'll see the phpMyAdmin login page;
**however, do not login yet.** We do not have
encryption enabled on the web page yet so your ISP or server
can theoretically see what you send. We'll setup encryption
down the road in a bit.

But first, we'll install the required PHP (libraries)

`$ sudo apt install php-gd php-json php-mysql php-curl php-mbstring php-intl php-imagick php-xml php-zip`

as well as NextCloud itself

```
$ wget https://download.nextcloud.com/server/releases/latest.tar.bz2
$ tar -xvf latest.tar.bz2 # unzip
$ cd nextcloud
$ mv ./* /var/www/html/ # move all files to webserver directory
$ mv ./.htaccess /var/www/html # move hidden dot files
$ mv ./.user.ini /var/www/html # ^
$ cd /var/www/html
$ chown -R www-data:www-data ./* # Recursively set www-data as owner of files
$ chown www-data:www-data .htaccess # ^ for hidden dot files
$ chown www-data:www-data .user.ini # ^
```

Now if you go to
`[server ip]`

you’ll get the Nextcloud page
**BUT DO NOT LOGIN / CREATE ACCOUNT**. Again,
we still need to set up encryption!

To set up encryption, we'll use certbot which we'll install and configure like this:

```
$ sudo apt install certbot
$ sudo apt install python-certbot-apache
$ vim /etc/apache2/sites-available/000-default.conf
```

then uncomment and change `ServerName`

to
whatever domain you’re using, such as
`cloud.mehvix.com`

.

Next, you need to point your domain at your server and open
your server to the external access. You’ll first need to
port forward your server (you can find the ip via
`hostname -I`

) to port `443`

, and then
create an A record under your domain’s DNS settings pointing
at your external IP (found via
```
dig +short myip.opendns.com
@resolver1.opendns.com
```

). **Note** that if you’re using Cloudflare,
you have to disable Universal SSL for Nextcloud to work.

After making your server open to the WWW, you can finish setting up certbot by restarting the webserver and activating certbot

```
$ systemctl restart apache2
$ certbot --apache
```

Following the set up dialog will prompt you to auto-redirect from http to https. Your choice, but I'd go ahead and choose redirect. Now you can login on the myPhpAdmin/Nextcloud securely!

We're not done yet though. For security reasons, it’s a good
idea to have the data folder outside of the web root
(`/var/www/`

) I already set up my drives/raid
config, so all I had to do was create and mount a partition:

```
$ mkdir /media/nextcloud-data/
$ chown www-data:www-data /media/nextcloud-data/
$ mkfs.ext4 /dev/sda
$ mount /dev/sda /media/nextcloud-data/
```

Then to set up automount, I did `sudo blkid`

to grab
the UUID of the partition which I then appended to
`/etc/fstab`

:
`UUID=[YOUR UUID] /media/nextcloud-data ext4 defaults 0 0`

From there, you can go ahead to the Nextcloud page, create
an account (you will use this for all NextCloud logins).
Make sure to change the data folder to
`/media/nextcloud-data/`

or whatever dir you are
using, then enter the MariaDB username (nextcloud) and
password.

**
Now you’ve got yourself a working Nextcloud
Server!**
However, there are still a couple things that you still need
to configure which I’ll cover in the following section.

If you head to
`https://yournextcloud.com/settings/admin/overview`

you’ll see all security and setup warnings. Here’s how you
fix the most common:

`vim /etc/php/7.4/apache2/php.ini`

`upload_max_filesize = 512M memory_limit = 512M post_max_size = 512M`

`systemctl restart apache2`

`vim /etc/apache2/sites-available/000-default-le-ssl.conf`

Add the following under

`ServerName`

:`<IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains; preload" </IfModule>`

`a2enmod headers`

`systemctl restart apache2`

`vim /etc/apache2/sites-available/000-default-le-ssl.conf`

Add the following under

`ServerName`

:`<Directory /var/www/html/> Options +FollowSymLinks AllowOverride All </Directory>`

`systemctl restart apache2`

`sudo apt install php-apcu`

`vim /var/www/html/config/config.php`

`'memcache.local' => '\OC\Memcache\APCu'`

`sudo vim /etc/php/7./mods-available/apcu.ini`

`apc.enable_cli=1`

`systemctl restart apache2`

`vim /etc/php/7.4/apache2/php.ini`

The following are default settings, consider checking out this blog for information on what each parameter means

`opcache.enable=1 opcache.enable_cli=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=10000 opcache.revalidate_freq=1 opcache.save_comments=1`

`systemctl restart apache2`

`sudo -u www-data php /var/www/html/occ db:convert-filecache-bigint`

`sudo -u www-data php /var/www/html/occ db:add-missing-indices`

First, update MariaDB:

`vim /etc/mysql/my.cnf`

`[mysqld] innodb_file_per_table=1`

`systemctl restart mariadb`

`mariadb`

`ALTER DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; use nextcloud; set global innodb_large_prefix=on; set global innodb_file_format=Barracuda; quit;`

Then, update Nextcloud:

`sudo -u www-data php /var/www/html/occ config:system:set mysql.utf8mb4 --type boolean --value="true"`

`sudo -u www-data php /var/www/html/occ maintenance:repair`

`sudo -u www-data php /var/www/html/occ files:scan --all`

`sudo chown -R www-data:www-data [data mount location]/[user]`

`sudo -u www-data php /var/www/html/occ files:scan -- [user]`

`sudo rm [username]/uploads`

`sudo chown -R www-data:www-data [data mount location]/[user]`

There are various other actions you can take to ensure that both your os and nextcloud are secure. I’ll be covering the one’s found on this wiki page.

`/dev/urandom`

On my system this ended up breaking Nextcloud, but I’ll keep it in here because it’s in the Nextcloud docs. If you’re site breaks, you should try commenting out

`open_basedir`

`sudo vim /etc/php/7.4/apache2/php.ini`

Add`open_basedir = /dev/urandom`

`systemctl restart apache2`

`sudo apt-get install php-fpm`

`a2enmod proxy_fcgi setenvif`

`a2enconf php7.4-fpm`

`systemctl reload apache2`

`vim /etc/php/7.4/fpm/pool.d/www.conf`

and add the following:

`pm = dynamic pm.max_children = 120 pm.start_servers = 12 pm.min_spare_servers = 6 pm.max_spare_servers = 18 pm.max_requests = 1000`

Note that these are the recommended settings for 4GB total/1GB database and should be modified for your system.

While theses tips aren’t needed for security, they’re nice to have.

`/index.php`

from every URL

`vim /var/www/html/config/config.php`

`'htaccess.RewriteBase' => '/'`

`sudo -u www-data php /var/www/html/occ maintenance:update:htaccess`

`systemctl restart apache2`

`vim /etc/php/7.4/apache2/php.ini`

`upload_max_filesize = 16G post_max_size = 16G max_input_time = 3600 max_execution_time = 3600 upload_tmp_dir = /var/big_temp_files/`

`vim /var/www/html/.user.ini`

`upload_max_filesize = 16G post_max_size = 16G`

`chown www-data:www-data /media/big_temp_files/`

`mariadb`

`set global innodb_file_format = BARRACUDA set global innodb_large_prefix = ON set global innodb_file_per_table = ON set global innodb_default_row_format = 'DYNAMIC' quit`

`vim /etc/mysql/my.cnf`

`innodb_file_per_table=1 innodb-file-format=barracuda innodb-file-per-table=ON innodb-large-prefix=ON innodb_default_row_format = 'DYNAMIC'`

`systemctl restart mariadb`

This is fixed, but I figured I'd keep it in anyways`$ cd /var/www/html/apps/ $ wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=1usRzYpaOVFwKn63xrCrtJTHuLpMXQz_j' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')& 1usRzYpaOVFwKn63xrCrtJTHuLpMXQz_j" -O rclick && rm -rf /tmp/cookies.txt $ rm -rf files_rightclick/ $ unzip rclick $ rm rclick $ systemctl restart apache2`

`systemctl restart apache2`

`vim /var/www/html/config/config.php`

Find your timezone in the ones available here.`'logtimezone' => 'America/Chicago',`

`vim /etc/php/7.4/apache2/php.ini`

`date.timezone = America/Chicago,`

`sudo -u www-data php occ config:system:set logtimezone --value "Los_Angeles"`

All files that are created for a user can be found in

`/var/www/html/core/skeleton/`

` ````
$ cd /var/www/html/
$ sudo -u www-data php occ maintenance:mode --no-check-certificate
$ cd /var/www/
$ rsync -Aavx html/ backupdir/html_bkup_`date +"%Y%m%d"`/
$ mysqldump --single-transaction -u [nextcloud] -p[password] nextcloud > backupdir/sqlbkp_`date +"%Y%m%d"`.bak
$ cd /var/www/html/
$ sudo -u www-data php occ maintenance:mode --off
```

]]>