False Proof: 1 = 2 (with Calculus)

Problem: Show 1 = 2 (with calculus)

Solution”: Consider the following:

1^2 = 1
2^2 = 2 + 2
3^2 = 3 + 3 + 3
x^2 = x + x + \dots + x (x times)

And since this is true for all values of x, we may take the derivative of both sides, and the equality remains true. In other words:

2x = 1 + 1 + \dots + 1 (x times)

Which simplifies to x=2x, and plugging in x=1 we have 1 = 2, as desired.

Explanation: Though there are some considerations about the continuity of adding something to itself a variable number of times, the true error is as follows. If we are taking the derivative of a function with respect to x, then we need to take into account all parts of that function which involve the variable. In this case, we ignored that the number of times we add x to itself depends on x. In other words, x + x + \dots + x (x times) is a function of two variables in disguise:

f(u,v) = u + u + \dots + u (v times)

And our mistake was to only take the derivative with respect to the first variable, and ignore the second variable. Unsurprisingly, we made miracles happen after that.

Addendum: Continuing with this logic, we could go on to say:

x = 1 + 1 + \dots + 1 (x times)

But certainly the right hand side is not constant with respect to x, even though each term is.


Chai – Functions with Applications

As usual, we invite the reader to follow along with the source code from this blog’s Google Code page, and to test programs with the interactive online interpreter.

One interpretation of a "Chai function."

Reserved Words, Values, and Ifs

In our last version of Chai we noticed a few things that needed fixing, so we’ll address those before moving on. First, consider the following chai program:

(with true false (or true true))

The user, being as odd as users will be, wants to bind the value “false” to the variable whose name is “true,” in a dirty attempt to trick somebody. The last version of Chai (see, Chai – Environments and Variables) interpreted this expression as “true,” and rightfully so in this mathematician’s opinion, since truth is not something which can be changed!

On the other hand, something just doesn’t feel right about this program. We can either allow such programs to be written (and wag a stern finger at all those who do!), or forbid the program from continuing once we find such a transgression. The latter makes more sense, and it introduces reserved words into Chai. As of the end of this post, our list of reserved words contains:

true false with if end pair fun

And we will add to this list as necessary. It’s ambiguous whether we should include things like +, -, *, /, etc. Someone may want to redefine what “plus” means, and that might not be an awful thing, say, once every two thousand years. For now, we will leave it alone. At least, we may stop someone from writing the appalling program:

(with with 1 (with with (with with with with) with))

All this requires to our code is that during parsing, whenever we inspect a variable binding (or list of variable bindings, or list of function arguments), we must ensure none are reserved words. This is straightforward enough, and the interested reader may inspect the source code.

Next, we realized ahead of time the need to discriminate between the input to our interpreter and its output. This is a direct result of our desire to add support for functions and basic lists. Now we have a new EBNF for “Chai values”:

chai-value = number
           | boolean
           | string
           | (pair chai-value chai-value)
           | end

One might notice that we have added strings, pairs (lists are nested series of pairs), and end, a null-value, named so to signify the terminating value of a list. In addition, we’ve added primitives for a whole host of operations, including: =, !=, <=, >=, <, >, pair (pair construction), end (list termination), first and rest (pair extraction), and end? (testing if a list is empty). We will certainly add more in the future, but for now this is enough.

In Racket code, we have the following new data type:

(define-type chai-value
  [num-v (val number?)]
  [bool-v (val boolean?)]
  [str-v (val string?)]
  [pair-v (head chai-value?) (tail chai-value?)]

The new discrimination between Chai expressions and Chai values affects every branch of our interpreter and in every primitive operation, but only in how we accept and package up our data types. The interested reader can check out the changes in the source code; we will not discuss them here. (But we will mention that none of the changes occur in the type-checker. Nice.)

Finally, we’ve added “if” expressions for control flow. In the EBNF, an if looks like:

expr = ...
     | (if expr expr expr)

Where the first expression is the “test” expression, and the second is the “then” expression, and the third is the “else” expression. This translates directly to Racket as:

(define-type chai-expr
   [if? (test chai-expr?) (then-branch chai-expr?) (else-branch chai-expr?)])

Because the symbol ‘if’ conflicts with the Racket language form, we rename it ‘if?’; note that in Chai programs, the language form is still “if”. The parser is straightforward for if expressions, so we’ll jump straight to the interpreter. The new clause is:

(define (interp input-expr env)
  (type-case chai-expr input-expr
    [if? (test-expr then-branch else-branch)
         (let ([test-result (interp test-expr env)])
           (type-case chai-value test-result
             [bool-v (test-value) (interp (if test-value then-branch else-branch) env)]
             [else (error 'interp "test expression was not a boolean: ~a"
                          (chai-expr->string test-expr))]))]))

Note that we need to ensure the result of evaluating the test-expression is a boolean, and we ignore the unevaluated branch. For instance, the following program will run in Chai:

(if true 7 (/ 1 0))

even when the else branch will throw an error as a standalone program.


Finally, we’ve reached a very important milestone in Chai: figuring out what to do with functions. Let’s start with the basic syntax. In EBNF, a function has the form

expr = ...
     | (fun (vars ...) expr)

Where “vars …” indicates the presence of zero or more identifiers representing input variables, which are collectively referred to as “vars”. For instance, here is a Chai program which evaluates to the identity function:

(fun (x) x)

In Chai, we will treat functions the same way we treat any other primitive value. One can pass them around as if they were a number. For instance, here is a function which accepts one argument x, and returns a function which accepts one argument y and adds x to y:

(fun (x) (fun (y) (+ x y)))

Applying this function to a number returns a new function. In some languages, functions are required to be defined “at the top level,” i.e., given special names and put in special places. Since Chai expressions still consist of a single expression, we don’t have anywhere else to put them. So if one wants to “name” a function one can, but as it is most functions will be “anonymous.”

Speaking of function applications, they look like:

expr = ...
     | (expr expr ...)

Where the first expression should result in a function, and the remaining zero or more results should result in arguments to the function. Note that syntactically, these “shoulds” are irrelevant.

So here is a program which applies the previous example, which we name “make+=”, to some values:

(with make+= (fun (x) (fun (y) (+ x y)))
   (with +=7 (make+= 7)
     (pair (+=7 9) (pair (+=7 14) end))))

The advanced readers will recognize this as an explicit form of “currying” (accepting a partial list of arguments). We may in the future decide to have Chai support implicit currying, so that we may rewrite the above program as something like:

(with +=7 (+ 7)
  (pair (+=7 9) (pair (+=7 14) end)))

But for now let’s focus on the implementation.

The Parser

In the parser, our new branches look like:

[(and (equal? 'fun first-thing)
      (equal? 3 list-len)
      (list? (second input-expr)))
 (let ([vars (check-vars (second input-expr))]
       [body (parse-expr (third input-expr))])
   (function vars body))]
 (app (parse-expr first-thing)
      (map parse-expr (rest input-expr)))]))] 

To elaborate, these two branches are wrapped within the condition that the expression is a list. If the first element of the list is not a reserved keyword like “with”, “if”, or “fun”, we assume the expression is a function application. Indeed, we could have some weird expressions that evaluate to functions, so we can’t discriminate any further here.

For functions, we have to ensure some things about the list of input variables. For instance, we can’t allow the user to repeat the same variable name twice, or use any reserved words, so we encapsulate both of those checks into a function called “check-vars”:

;; check-vars: listof symbol -> listof symbol
;; check a list of variable names for invalidity
(define (check-vars names)
    (map (λ (name) (when (reserved? name) (parse:bad-identifier name))) names)
    (when (has-duplicates? names) (error 'parse "list of variables has duplicates: ~a" names))

We leave the implementation of has-duplicates? as an exercise to the reader (with the solution in chai-utils.rkt), and reserved? is just a membership test in a list of symbols (in chai-ast.rkt).

While it has its details, the parser is the easy part of implementing functions. We didn’t actually have to make any important decisions. We’ll see the semantics are a bit more complicated.

Scope, and Closures

In general, the scope of a variable x refers to the places in the program where we can inspect the value of x. For instance, in the following program x is only in scope within the “with” expression.

(with y (+ x y)
  (with x 7) y)

So, in fact, the reference to x before the nested with statement is invalid, and this program will raise an error upon evaluation. Note that y is in scope in the nested with. Recalling how our interpreter works, this is obvious, because we only augment the environment with new variables after encountering their definition in a with.

To illustrate the confusion arising in discussions about scope, we have to investigate functions. In particular, the definition of a function can refer to a variable defined somewhere else, but the body of the function isn’t evaluated until it’s actually called. Since functions can be passed around like numbers, it raises ambiguity as to which variable we should refer to during a function application.

For instance, we could write the following program and ask what it should do:

(with my-function (with x 33
                     (fun (y) (+ x y)))
   (with x 44 (my-function 55)))

Should this program evaluate to 88 or 99? Before continuing, make a guess as to what will happen (and try to argue why your choice is the best option), and verify it by running the program through our interactive interpreter.

In fact, there is no completely “correct” answer to this question, and it highlights the differences between lexical and dynamic scope. In the latter, a variable will always refer to the most recent binding which is in scope. In other words, if Chai had dynamic binding, the program above would evaluate to 99. On the other hand, if we implemented lexical scope, “my-function” would retain the information about what variables were in scope during its definition, and so the resulting call would evaluate to 88.

The engaged reader will have by now verified that Chai has lexical scope, and not dynamic scope (in the future, we may provide an alternative version of Chai which has dynamic scope). Most contemporary programming languages implement lexical scope, because it just so happens that otherwise the logic flow in a program is harder to determine just by reading it. However, some popular languages originally had dynamic scope, and later decided to tack on support for lexical scoping. Sometimes lexical scope is referred to as static scope.

In order to support lexical scope in Chai, we have to now discriminate between a function definition, and the function definition combined with the variables that are in scope during definition. In other words, we need the result of interpreting a function definition to contain both pieces of information. In particular, we call this piece of data a closure.

Now the chai-value datatype has an additional branch:

chai-value = number
           | boolean
           | string
           | (pair chai-value chai-value)
           | end
           | (closure environment (vars...) chai-expr)

Where the environment is our datatype for variable bindings, the vars… is the same list of function variables from our function definitions, and the chai-expr is the body expression, in unevaluated form. (Indeed, we can’t evaluate the body until it’s applied to some arguments!)

So this adds one more branch to our type-case:

(define-type chai-value
   [closure-v (env environment?) (vars (listof symbol?)) (body chai-expr?)])

Now we are ready to interpret functions, and see what we can do with them.

The Interpreter

In fact, interpreting function definitions is easy! Since we have already been keeping track of the environment all along, we can package it up in a closure in a single line:

(define (interp input-expr env)
  (type-case chai-expr input-expr
    [function (vars body) (closure-v env vars body)]))

Applying a function is quite a bit more work. In particular, we have two things we need to check before believing that the user knows how to write programs:

  • Is the first thing in a function application a function? (Does it evaluate to a closure?)
  • Does the number of provided arguments match the number of accepted arguments?

If neither of these are the case, we need to raise an error. Assuming these requirements are met, we can perform the actual function application as follows:

(let* ([interped-args (map (λ (arg) (interp arg env)) args)]
       [new-env (foldl add-binding closure-env vars interped-args)])
  (interp body new-env))

where args, vars, closure-env, and body are all the expected parts of our function application and closure. Note that we are very specific to augment the closure environment with the new bindings for the function arguments. This is the key of lexical scope, whereas dynamic scope would use the standard environment.

With all of the case-checking, the branch in our interpreter is a bit longer:

(define (interp input-expr env)
  (type-case chai-expr input-expr
   [app (clo args)
         (let ([expected-clo (interp clo env)])
           (type-case chai-value expected-clo
              [closure-v (closure-env vars body)
                         (let ([arg-count (length args)]
                               [var-count (length vars)])
                           (when (not (eq? arg-count var-count))
                             (error 'interp "expected ~a args for a function application, but got ~a" var-count arg-count))
                           (let* ([interped-args (map (λ (arg) (interp arg env)) args)]
                                  [new-env (foldl add-binding closure-env vars interped-args)])
                             (interp body new-env)))]
              [else (error 'interp "invalid function in function application: ~a" (chai-expr->string expected-clo))]))]))

Try to follow the logic above. Even for the author, determining what someone else’s code does is a nontrivial process, so don’t worry if it’s overwhelming at first. Just remember that most of it is error-checking, and the key part is the three lines where we actually perform the function application. Of course, to get an even better understanding, try to run programs in our interactive interpreter which hit every error case, and compare that with the flow of logic in the code above.

Recursion, But Not Quite Easy Enough

The experienced reader might be asking herself “Why haven’t we implemented any loops yet? We can’t really do anything interesting without loops.” And she is correct! A programming language cannot be Turing-complete (informally, have the true power of a computer) if it can’t branch out, or make programs which run indefinitely.

As of now, we couldn’t, say, write the function map (see our functional programming primer, A Taste of Racket, for a definition). If we try, we get a nasty error:

(with map (fun (f list)
            (if (end? list)
                (pair (f (first list)) (map f (rest list)))))
  (map (fun (x) (+ 1 x)) (pair 7 (pair 6 (pair 5 end)))))

It complains that “map” is not bound in the current environment, which is a shame, because map is a really great function to have around, and we can’t build it yet (well, that’s not quite true, but it wouldn’t be as straightforward as what we have above).

It turns out that even though we can’t quite do this kind of recursion with names, we can still achieve infinite loops in Chai! We challenge the reader to figure out how to do it, and see what our interactive interpreter does in response. As a hint, the shortest solution the author has found is a mere 33 characters long, including spaces and parentheses, and it only requires functions and applications.

Some Further Issues to Address

We recall saying we would like the user to be able to redefine what “+” means via a with expression. However, upon trying to evaluate the following program which “adds” two functions, we get an error:

(with + (fun (f g) (fun (arg) (+ (f arg) (g arg))))
  ((+ (fun (x) (+ x 1)) (fun (y) (* y  2))) 7))

In order to fix this, we need to stop distinguishing between primitive operations and functions which are bound to symbols in our environment. In other words, we want to pre-populate the environment with bindings for primitive functions, which can be overwritten by the programmer. This will simplify our code significantly, since we can move even more of the logic outside of the interpreter and parser, and think of everything as a function application. This is something we have to look forward to in the future.

As long as we’re thinking of everything as a function application, why don’t we simplify Chai even further so that we only have functions and applications? No numbers, no primitives, no conditionals. What sort of language would we get? We’ll see just what happens with a little intermezzo next time. Of course, we will only do this temporarily to explore its theoretical properties, and then we’ll return to the civilized world.

Until then!

How to Take a Calculus Test

A Detailed Reference

As my readers may know, part of my TA duties include teaching a discussion section, and this semester it’s Calculus. We’re in week 9 now, closing in on the second midterm exam, and frankly put: my students suck at tests. I know they know the material, and they’re certainly not any dumber than any other random group of students, but they just can’t seem to score well on in-class exams and quizzes.

So today I staged an intervention, called “How to Take a Math Test.” In it I provided a list of ten items about test-taking strategies for math, but I like to think of them as an insight into how graders grade for these elementary college courses. A quick Google search for “how to take a math test” gives a pitiful list of unhelpful pointers, mostly about how to study for a test, and not what to write down on a test to show your knowledge.

What I’m not going to cover are the stupid obvious things, like: go to class, do your homework, study, and don’t fall asleep during an exam. If you have no idea what’s going on in lecture or discussion, then you have much bigger problems than test-taking skills, and you should go read your course textbook before reading this article. Here we care more about the question of what to do when you have no idea how to approach a problem, but know the general techniques for solving other problems.

Here is the complete list, without explanation.

How to Take a Calculus Test

  1. Show what you know.
  2. Don’t invent new math.
  3. Don’t contradict yourself.
  4. Do the easy questions first.
  5. If you don’t know how to do a problem, start by writing down relevant things that you know are true in general.
  6. Break difficult problems into manageable pieces.
  7. Know what a function is, and know what things are functions.
  8. If you aren’t taking a derivative, it’s probably wrong. (see the explanation below)
  9. If you’re doing obscene amounts of computation, it’s probably wrong.
  10. Don’t care about the final answer.

Point by Point

1. Show what you know.

This is really the most important part. As a grader, we start by assuming that a student knows nothing about Calculus, and it’s the student’s job to prove us wrong. For instance, if you’re being quizzed on the derivative of 9^x / 3^x, and you don’t see any nice simplifications, then you should be writing down something like the following:

\frac{d}{dx} (9^x) = 9^x \ln(9)
\frac{d}{dx} (3^x) = 3^x \ln(3)
\displaystyle (u / v)' = \frac{u'v - uv'}{v^2}

Suppose at this point you have a brain aneurysm or mess up horribly on the subsequent algebra, whichever is more frightening. Suppose further that your professor still wants to grade your test. Even though you left the rest blank, or completed it with a boatload of errors, you’ve pointed out to the grader that you understand how to take the important derivatives, and you know how to piece them together. That accounts for the majority of credit on this problem.

2. Don’t invent new math.

In other words, use what you know, and only what you know. I had a student make an very creative “simplification”:

\textup{arccot}(xy) = \textup{arccot}(x) \cdot \textup{arccot}(y)

I know this student has never seen such an identity before, because it’s simply false (try, for example, x = y = 1). He seemed to be confused with properties of logarithms and properties of trigonometric functions, but in any event he couldn’t have been too confident that this was true. The same goes for any identities: if you aren’t 100% sure they work, either try plugging in easy numbers to verify they don’t work, or just ignore that idea. Not all problems need an algebraic trick before the calculus comes in. At least in a first course on calculus, algebraic “tricks” are usually never needed to solve a problem.

3. Don’t contradict yourself.

Sometimes this is obvious, and students do it anyway. For instance, on a midterm a very large number of students approached the following problem by plugging in numbers and claiming the conclusion. Here is a mock version of a student’s answer.

Use the intermediate value theorem to show that there exists a solution to the equation \sin(x) = 1-x on the interval [0, \pi].

\sin(0) = 1 - 0 \ \checkmark
\sin(\pi) = 1 - \pi \ \checkmark
So a solution exists.

This answer makes mathematicians cry. If this student knows trigonometry (and they are assumed to know it, as a prerequisite to this class), then they certainly see that they just claimed 0=1! From a grading standpoint, there is nothing in this solution that implies the student has any idea what’s going on. He might vaguely remember that he’s supposed to plug in the endpoints somewhere, but he didn’t even stop to check if what he wrote down made sense.

Here’s a subtler example. My students freaked out when they had to find the derivative of |x| on the midterm, so every day in lecture the professor reminded them how to do it. On the next week’s quiz, he put the same problem on there, and here’s what many students wrote:

\displaystyle \frac{d}{dx}(|x|) = 1 if x \geq 0,
\displaystyle \frac{d}{dx}(|x|) = -1 if x < 0,
and it does not exist when x = 0.

Here the contradiction is that the student claimed that the derivative both exists and does not exist at x=0! Note the “or equal” part of \geq above. If this were a history essay, the analogue would be claiming Richard Nixon was the 37th and 38th president of the United States! It doesn’t matter what the correct answer is, because what you wrote is always false.

4. Do the easy questions first.

This one should be self-explanatory, but it still causes issues. Pick off the rote derivatives at the beginning of the test, and focus on the word problems later.

5. If you don’t know how to do a problem, start by writing down relevant things that you know are true in general.

This one is in the same realm as showing what you know, but let me express it in a different way. Suppose you are asked to find the values of x for which the following ridiculous function is increasing and decreasing:

\displaystyle f(x) = \ln(7^{\sqrt{\arctan(x^{99})}})

Of course, you’ve probably just had a heart attack, but if you’re still alive you should start by writing down what’s true about functions in general:

A differentiable function is increasing when its derivative is positive, and decreasing when its derivative is negative. So we need to find when f'(x) > 0, and f'(x) < 0.

The hope on the educators part is that this will help you figure out on the spot what to do next. Oh, so we need to take a derivative? How would I start taking the derivative of this massive thing. Well I know the derivative of \arctan(x) is 1/(1+x^2), but that’s an x^99 so there will be a chain rule somewhere… The more little pieces of information you can get across that you know, you’ll both get a higher score and closer to a correct solution without scribbling down a bunch of nonsense and hoping the grader doesn’t look.

But even with the rest of the problem left blank, this is close to half credit (depending on the grader). In general, half the credit goes to conceptual understanding, and the other half goes to technical ability (your ability to arrive at an answer and do algebra). Usually students know these general facts, but for some reason never write them down on a test. They think that if they can’t use it to find the answer, then they can’t get any credit for the knowledge. As I’ve been saying all along, it’s quite the opposite.

That being said, if you just write an essay about how to do problems in general and don’t even make an attempt at using it to solve the problem at hand, you can’t expect that much credit.

6. Break difficult problems into manageable pieces.

Using the same example above in 5., even if you don’t have the stomach to compute the entire derivative (who does, really?) you can still show your knowledge by computing parts. For instance, you might start by writing down the following

d/dx(x^{99}) = 99x^{98}
d/dx(\arctan(x)) = 1/(1+x^2)
\displaystyle d/dx(\arctan(x^{99})) = \frac{99x^{98}}{1+(x^{99})^2}

With just those three lines, you’ve shown you know the chain rule, the power rule, and the derivative of inverse tangent. Even if you don’t finish, you’ve got most of the points already.

7. Know what a function is, and know what things are functions.

This one is always alarming. I had a student who wrote the following thing:

\arctan(xy) = \textup{arc}(xy) \cdot \tan(xy)
x = y = 1, \textup{ so } \arctan(1 \cdot 1) = \textup{arc}\ 1 \cdot \tan \ 1 = \textup{arc} \cdot \textup{tan}

It’s alarming how often something like this crops up. If this doesn’t make you laugh or feel pity or shock (and you’re about to take a math test), then you should seriously consider taking a few minutes to figure out why this makes absolutely no sense.

On a finer point, this is the same as what is quite often written below:

d/dx(\sin) = \cos

This is nonsensical for just the same reason. Sine and cosine are functions, not your average values you can do algebra with. The argument to a function is crucial, and omitting it as above leads down the slippery slope of saying \arctan = \textup{arc} \cdot \tan.

8. If you aren’t taking a derivative, it’s probably wrong.

More precisely, if you aren’t doing what you’re supposed to be tested on, it’s probably wrong. On the quiz that inspired this intervention, many of my students took the entire quiz (and got answers!) without taking a single derivative. Tests, and quizzes especially, are mostly meant to be a straightforward verification of your comprehension of the course material. If you don’t use the material at all, you’re quite likely to fail.

9. If you’re doing obscene amounts of computation, it’s probably wrong.

Tests are designed to be doable and to work out nicely. If you find yourself writing an essay’s worth of square-root manipulations and analyzing eight cases, you probably made a mistake in your initial calculations. For my students, it was mixing up inverse trigonometric derivatives.

10. Don’t care about the final answer.

This might be the hardest for students to swallow, but it’s true. Nobody cares about the derivative of some contrived function at x = 4. As a grader, I don’t even look at the final answer until I’ve verified your steps are correct, and only then to make sure you didn’t forget a negative sign. The truth is that mathematics is about the argument, not the answer.

For instance, if you find yourself with no time left and you boiled down your algebra to the alarming expression 1/(1-1), don’t don’t don’t just ignore it and call it equal to 0. You will get more partial credit by recognizing that your result is nonsensical and saying that than you will by ignoring the obvious problem. What you should say is something like:

I recognize that this doesn’t make sense, because you can’t divide by zero, but I don’t have time to fix it. I think mixed up the derivatives of secant and tangent.

Of course, if you have a little extra time, you could continue to write what the derivatives should have been. This shows the grader that you are smart enough to recognize your own mistakes. In fact, if I were grading such a problem, I would go out of my way to give more credit to a student who wrote such an explanation and got no final answer than the student who just wrote 0 or “does not exist.”

A Word About Math Education

A first course in calculus is not interesting mathematics. In fact, I was among those students that abhorred calculus because it was nothing but monotonous repetition. It’s an unfortunate truth of education in the United States that students are tortured in this boring, dry way.

The only time in my life I actually enjoyed calculus was when I programmed a video game in high school, in which the characters needed to jump. I modeled their velocity as the derivative of an appropriately constructed parabola. Now that was cool, and it got me inspired enough to seek my math teacher’s help when I wanted to rotate a curve representing a bullet’s trajectory to an arbitrary angle. That’s when I started to pay attention to how math can lead to elegant computer programs.

There are a lot of things one can do with these elementary ideas in calculus. Unfortunately, the subject is shoved down our throats before we have an appetite for it. So the advice I have is to be strong, and get through it. At the very least, you’ll be smarter for juggling all of these ideas and details in your head.

Eigenfaces Talk – Wednesday at 4p

So it may seem like I’m not doing much related to my blog these days (in fact, midterms have been quite ruthless these past two weeks, and I’m not out of the woods yet!), but I’m actually giving a talk on eigenfaces on Wednesday for the UIC undergraduate math club. If any UIC people happen to be reading this and want to come watch, it’ll be at 4pm in SEO 300 (refreshments provided, I believe).

Of course, it’s not original research, but if it’s certainly good practice in giving a lecture, and in preparing a Beamer (LaTeX) presentation. On the other hand, eigenfaces are only 20-year-old mathematics, so compared to many topics it’s quite fresh 🙂

To my readers in other parts of the world, I assure you I am still here and writing. Some future posts I have in mind include more Chai development (but it requires some significant server-side changes to the online interpreter), using search algorithms to find proofs in the propositional calculus, an entry in the proof gallery on the Banach-Tarski paradox, and an investigation of the randomized greedy algorithm as a heuristic for NP-hard problems (and artistic design!).

I just have to make sure I pass all my classes too, okay?