 march our way along, keeping track of a vector of r coefficients of h as we go. And the initial vector is very easy to compute, because when k is negative, all of those coefficients are 0, because these are polynomials. And we can compute the very first, the one non-zero coefficient we want is h sub 0 to the n. And we can compute that very quickly using binary exponentiation, especially if we're working in a finite field, although these are just formal definitions that we're thinking about over z for the moment. OK, so that was sort of just the general setup. And this would be a very useful slide to remember. And it's OK if you forget it, because when you come to week three, I'm very sure David Harvey is going to have a slide that looks almost exactly like this when he starts talking about average polynomial times for computing zeta functions. This is sort of the fundamental recurrence that gets used over and over again. Of course, things get a lot more interesting when there's more than one polynomial floating around, or you're not working with a polynomial in one variable. Today, we're just going to focus on the absolute simplest case where now we want to specialize to the case where h is our cubic polynomial f that defines our elliptic curve y squared equals f of x. So r is 3 from now on. We're not going to worry about r anymore. We're always going to be working with r as just 3. So we have a cubic polynomial. We're going to assume that the constant coefficient of f is not 0. In fact, if it turned out to be 0, that would be good news. We could make the algorithm faster. But just to keep everything simple, we'll just say, well, if it is 0, replace f of x with f of x plus 1. And if you're in characteristic 3 where it's to be potentially too small for that to be guaranteed to work, go count points using our naive algorithm, because we're only talking about finite fields. And the n that we're interested in, the superscript on all of this screen, this was completely generic. It would work for every n. But the n we want is p minus 1 over 2. Because recall that the Haase invariant is the coefficient of x to the p minus 1 in the n-th power of f, where n is p minus 1 over 2. And so our goal for computing the Haase invariant is to compute f sub p minus 1 superscript n. That's the coefficient we're after. OK. And so now we're going to do, we're just going to modify this linear relation a little bit. So this linear relation, as it stands now, equation 1, has an n in it. Not just in the superscript, but actually in n. The integer n is appearing as a coefficient in the relation. And we'd like to get rid of that for reasons that will become clear later. And so there's sort of a clever way to get rid of that would be to multiply the numerator and the denominator the right-hand side by 2. That's why I wrote this strange thing, multiply it by 2 over 2. And so we get a 2 showing up in the denominator. And we had a 2 times n plus 1 inside the sum end. But we're ultimately only going to be interested in what's happening modulo p. Because remember the Haase invariant is something that's defined over fp. It only makes sense modulo p. And when we reduce modulo p, the n just disappears. So that was the reason for multiplying the numerator and the denominator by 2. We get a slightly simpler expression. Because 2n plus 2 is 1 mod p. And if we now define our initial vector, which we should secretly be thinking about as 0, 0 f sub 0 to the n, but it will actually be convenient. Because we want to have a relation that really doesn't have any n's in it at all. I'm going to write it as just a 0, 0, 1. And we'll throw in the nth power of the constant coefficient of f later. So we're going to consider an initial vector, the vector that we're going to be moving along through our linear recurrence, 0, 0, 1. And then we have a 3 by 3 matrix that expresses the recurrence relation that's written directly above. So you can just write down this matrix. From looking at this relation, of course, we have a denominator of 2 times k, 2k f of 0 that we need to worry about. So it's important that k is not 0. And this is why we wanted f0 to not be 0, so that we're not dividing by 0. But since we're eventually computing, interested in what's happening modulo p, well, if we're going to end up computing a product of p minus 1 of these matrices, of these m sub k's, y p minus 1, because we're trying to move from f sub 0 up to f sub p minus 1. And so we need to apply this recurrence relation p minus 1 times. But if we take the product of 2k f sub 0 as k goes from 1 to p minus 1, we get just minus 1 mod p. Because theorem is a little theorem, and then we're taking p minus 1 factorial mod p is just minus 1. And this implies this now gives us a formula for computing the Haas invariant. We're going to compute the coefficient of x to the p minus 1 in the nth power of f modulo p using this by applying our linear recurrence, applying it to v naught, our starting vector, and then multiplying by minus the constant coefficient of f raised to the nth power. And that will give us a sequence of three adjacent coefficients of f to the nth power. And it's the last entry is the one we want. f sub p minus 1 superscript n is exactly the coefficient. We're looking for that's where the Haas invariant is. Any questions on any of this? OK, great. I mean, hopefully this seems like blindingly simple to you. Yeah, question? Sorry, say that again? It's potentially a lot of matrices. Yeah, yeah, absolutely. This is potentially a lot of matrices to multiply. In fact, we can say exactly how many matrices we have to multiply, p minus 1 of them. So that's our next task is to multiply a bunch of matrices. But I'll also note that we're actually trying to compute a vector times a bunch of matrices. And so at least if we were really going to do it step by step, one at a time, we'd be better off doing a vector matrix multiplication all the way along. Multiply this vector times the first matrix that gives us another vector multiplied by the next matrix. So at least we're doing vector matrix multiplication rather than matrix matrix multiplication. Of course, all of this is O of 1. 3 by 3 is O of 1. So we're not going to worry about things like what's the complexity of matrix multiplication. I mean, they're 3 by 3 matrices. It certainly doesn't take more than however many, whatever number of multiplications you want to spend, it's going to be O of 1 in our complexity bound. All right, but I think this one slide or this example really captures what underlies all of the algorithms, all of the average polynomial time algorithms for computing. Zeta functions are in some sense expressed in the slide. And the key was getting rid of any dependence on n and p in this expression. We're going to apply it mod p first. But you'll notice that the matrix m sub k doesn't have any p's in it. It just has coefficients of f and it has indices k. And I could use the same m sub k modulo many different primes p. The matrix is not going to change if we were to imagine that f was actually an integer polynomial. But let's first focus just on let's not try to do two things at once. Let's first just focus on the simpler problem. I have a specific elliptic curve over fp defined by y squared equals f of x. And I want to compute its Haase invariant. How would we do that using this method? It's not going to be as fast as Mestre's algorithm, the spoiler alert. We're not going to get a faster algorithm. But it's useful to understand how this algorithm works because once you go to higher genus curves, it actually will give a faster algorithm than any other algorithms we know. So let's first just consider the completely straightforward as we did with our naive point counting algorithm. Our first algorithm will be let's just compute directly from the definition. So we have an expression here, expression equation, two that tells us how to compute the Haase invariant. What if we just applied that equation? So what would happen? We get an algorithm to compute the Haase invariant using vector matrix multiplication. It takes as input a monic square free cubic polynomial over fp that defines an elliptic curve. The fact that it's square free guarantees that with non-zero constant coefficient. We compute our initial vector is 0, 0, 1. And then let k go from 1 to p minus 1. And we just iteratively replace our vector v with v times m sub k, the next matrix in the list. We're just going to march along, applying our linear recurrence until we've done it p minus 1 times. And then when we have, we have a vector with three entries and recall that it was the last entry we wanted, the third entry. So we're going to take the third entry of v, multiply it by minus f sub 0 to the n. But I've put in parentheses around the f sub 0 to remind you that no, no, you don't need to compute f to the n and then take the constant coefficient, take the constant coefficient and just raise it to the nth power. And so what's the time complexity of this? Well, we're doing basically O of 1 ring operations at each step, and we're doing p minus 1 steps. So the complexity is going to be essentially p multiplications in fp and the complexity of multiplying two elements of fp, because we know integer multiplication has complexity O of n log n. It's going to be p times log p log log p. Now there's a couple of things you can do to improve the constant factors. One thing is when we're going from one matrix to the next rather than evaluating doing multiplications to compute the matrix entries, because the matrices themselves are just linear shifts of each other, we could compute each successive matrix using finite differences. And the fact that there are a lot of zeros in this matrix, although it's only three by three, so there aren't so many, but imagine this were like 100 by 100 version of the same thing. There would be a whole lot of zeros because there's only going to be whatever the dimension is, it's a linear number of non-zero entries. So these actually absolutely make no difference in the elliptic curve case, but for higher genus curves, these optimizations are quite useful. OK, so let's go ahead and look at the algorithm. Not that one, this one. So this is a case where the algorithm looks a little bit more complicated in when implemented. This is a magma implementation, then it did on the slides, but that's in part because I went to the trouble of actually implementing the optimizations that I mentioned. OK, so what is this doing? It's taking as input an elliptic curve E. It's first figuring out what finite field we're working over, what's the base ring of E, and what's its cardinality. And because our goal is to count points on E, we can only do that when P is bigger than 13, otherwise knowing the trace for binius mod P is not enough. But we'll just farm that out to an algorithm we've already implemented when P is smaller than 13. We're then going to get the cubic polynomial F that defines E, the called the Vierstrass model function here actually should really be called short Vierstrass model. It's going to put our elliptic curve in the form y squared equals x cubed plus a x plus b. And the function hyper elliptic polynomials just asks for a pair of polynomials that define E. But once we've put it in this y squared equals x cubed plus a x plus b, there's just one that we care about, and we'll call that F. That's F is our cubic polynomial. Now, there's no guarantee it has zero constant coefficient. So we're just going to make it not, it doesn't have non-zero constant coefficient. We're going to make it non-zero by just continually shifting F, replacing F of x with F of x plus 1 until the constant coefficient is non-zero. And this is guaranteed to work because F is a cubic, and we already know we're in characteristic bigger than 13. So there's definitely enough elements of Fp floating around for this to work. And this parent F dot 1 here is just a way of finding out what is the variable, the indeterminate, the x in the polynomial ring that F lives in. OK, my function here never defined x. So if I just wrote an x here, it wouldn't work. In GP, it would. That's the magic of GP. You can just create an x out of thin air without ever having to define it. OK, so this is giving us our polynomial F. We're then going to set up our initial vector, 0, 0, 1, which I want to live in Fp, so all the arithmetic is happening in Fp. So this is.