 For those of you who are disappointed that you couldn't look at the Ramified Prime counts in the list of number of fields, this was fixed very quickly yesterday. Edgar posted an issue on our GitHub or somebody in the audience posted an issue on our GitHub repo and it was actually fixed before I got back to my hotel room. So if I go here I can look at the Ramified Prime counts and since I'm feeling brave I'm even going to sort in reverse order. So take a second. There are 21 million number fields but we can find out which number field in the LMFDB has the most ramified primes. And there it is, a number field of degree 43 and a deceptively simple equation but we can see all the primes that are ramified listed there. Okay. The other thing I wanted to mention, I guess apologize for is I know many of you ran into difficulties during yesterday's problem session. This was not necessarily due to a bug but it was really a battle to the death between two web servers Apache 2 and our Jupyter Hub web server that were happy to coexist when there are only a half dozen or a dozen people accessing them but when they got hit with 100 students at once they each started fighting over who should be taking charge. We chose our Jupyter Hub server so Apache is now gone from our server and everything seems to have been working smoothly throughout the day. So I invite you to, you're welcome to follow along in this lecture, my hope is that everything will work fine. The lecture notes, the link to the lecture notes is posted on the Zulip. One note also and this is relevant to the problem sessions, when you're opening things that are in the course folder, which all the links I posted to the Zulip will be and even in here if I go into links and pull up notebooks, you can look at them, you can even run code snippets and make changes but you can't save anything because you don't have right access to the course folder directory and that's a good thing because otherwise your changes would instantly be visible to everybody else and then you'd start competing with each other to save your changes. So when you're doing a problem in the problem session, the first thing you want to do with a notebook that's linked to in one of the problem notes is to when you go to save it you can also say I want to save this in a different place and then you should save it in your default directory, your home directory up top where it'll be private to you and you can make whatever changes you want and no one else will be able to see them. If instead you would like other people to see them or you would like to share your notebook with someone else, one thing you can do is make a copy of it. You shouldn't use this as sort of your primary editing spot but you can make a copy of it to the shared folder but the shared folder is like sort of a big table with random stuff on it in the middle of a huge room that people walk by every day. Anybody can put anything they want on the table and anybody can take anything and there's no safeguards in place to prevent people from doing the same thing at the same time so really the way to use the shared folder is really as put your file there with some distinctive name and then you could let people know it's there but try not to edit files that are there unless unless I guess if you're the one who put them there. For those of you say wait I want to do collaborative notebook editing I want to be able to have me and all my collaborators be you know type working away in the same notebook at the same time changing cells left and right there's a wonderful resource that will allow you to do that online it's called Co-Calc and I highly recommend it it's very well suited to this that kind of collaborative interaction our Jupyter Hub notebook is not. All right with that out of the way let's begin the goal for today is to sort of take an initially initial step dip our toes into the world of actual actually writing code in each of these different computer algebra systems before I do that one of the last announcement I wanted to make is I understand that not everybody was able to complete the scavenger hunt because of some of the technical issues we ran into I encourage you to do so whenever you have time it doesn't it doesn't take that long when the server is actually working correctly and you can access everything without a problem and as I said yesterday if I had one goal for the course it would be to get everybody through the scavenger hunt and if you're if you're needing any motivation or maybe even a hint let my t-shirt inspire you okay all right with that out of the way let's get started so when it comes to turning math into algorithms or you know turning something you might have some cool example or maybe some cool results or something you want to investigate that you read in a paper you want to open up your computer algebra system and start playing with it you're now in the position of trying to turn math into code and one thing to keep in mind for those of you may not have done this before is that what makes for a good mathematical definition doesn't necessarily make for a good algorithm or a good way to work with an object in a computer algebra system and so I thought I just start with a very simple example something familiar to everyone let's let's consider the Fibonacci numbers which have a very simple elegant beautiful mathematical definition moreover it's a definition that lends itself immediately to an algorithm so let's go ahead and write an algorithm to compute phenomena to Fibonacci numbers so I'm gonna start out with I'm just doing these in alphabetical order I'll start out with GP by the way today is the only day where I'm gonna take you through the ordeal of doing something in every single one of these systems after today the instructions will be just pick whichever one you like the best okay or whichever one you'd like to learn maybe not the one you like the best okay so in GP we want to define our Fibonacci function so and this is one exercise where I'd ask you don't follow along with me don't type this function in yourself and don't run it you'll see why okay you're you're welcome to type it in just don't run your function because remember I said this was an example of how definitions don't always make for good algorithms so what am I doing here so in I'm creating a function in GP that I'm going to call fib for short for Fibonacci fib of n and what is this function well the equal sign is telling you what it is it's a function that is if n is less than or equal to 2 I'm not going to worry about negative n if the user inputs in a negative number they get what they deserve which is 1 for positive numbers so they'll get if it's 1 or 2 that it should return 1 so that way and if then else construct works in GP is if looks like a function that takes three arguments the first thing is the condition to test the second thing is what to do when that conditions true in GP these are called closures these are you know any argument can actually be code that gets executed and then the third argument is what to do with the conditions not true so if n is less than or equal to 2 it should return 1 otherwise it should execute the Fibonacci recurrence and need to close the parentheses and that's it and just as good software engineers we will write a quick test just to test it on some small cases and make it sure it gives us the answer we think it should hopefully I got all the parentheses right I didn't yes I can is that good enough unexpected thank you yeah making it bigger helps me to great they printed out I think look like the first 10 Fibonacci numbers okay let's go ahead and compute the hundredth well I mean it's a hundred not ten right it'll take it should take a little longer maybe let's let's give it some time well maybe GP is just slowly computing for a notch number so let's let's switch over to magma maybe magma can do this better I know magma a little a little better so I'm less likely to make typos I'm as likely to make typos when I'm less likely to make language errors and I know I'm writing magma codes so I have to put a semicolon after everything got it in one okay let's see if magma can give us the hundredth Fibonacci number no joy let's let's try Oscar Oscar's newer so maybe Oscar knows how to compute and Julie is supposed to be really fast question mark if I can type it and this is yeah this is like programming in teams except I have a team of a hundred looking to catch my typos so please shout them out when you see them this is how you specify a range and Julie that's one colon ten I'm gonna remember to use printlin because if I use print it doesn't put a carriage return at the end and that's always annoying in every other in all the other systems print will add a carriage return it's working let's see if there's anything going on Edgar we'll go over to Sage now which I can do it this way and I remember in Sage because we're using Python 3 if I want to print something I have to put parentheses around it I can't like I said could in Python 2 where I could have just said print space for then okay let me go see if I restart my Julia colonel this is what you should do if you have a colonel it doesn't seem to be doing what you think it ought to be doing this is actually straight Julia code I didn't type using Oscar because I actually know that Oscar the Oscar hasn't had any added any functionality that I'm actually using here we'll see an example an example when we do the Sivware a little later when we do sums of cubes where I do need to do using Oscar okay so it looks like it was just my colonel was in a weird state in general it's you know just when anything goes wrong turn it off and turn it on is always a good the first step in any tech problem restarting your colonel is the equivalent of turning things on and off okay well maybe my now GP is finished how about magma nope nope nope and if you happen to I didn't try it here let's sorry let's do it and if you happen to be logged in you could even go to the console yeah let's let's show you how to do that I could go here and I could go to the terminal and I could do top and I could see that there hopefully there should be somebody working away a little confused as to why they're not but they are computing they are doing some oh yeah there they are I say sorry my eyes are a little so you probably can't see that because it's way too small but there are several jobs running at a hundred percent which makes me think that a few of you didn't follow my instructions and actually did type this in but that's okay this is a big server it can handle half a dozen of us doing the wrong thing but not a hundred okay so I'm gonna abandon this and give up I'm gonna stop the colonel on all of them some of them will actually give you information when you stop telling you something useful about what it was trying to do so they go into Sage trims the stack dump but in GP you can actually see that it was you know fairly deep but not really that deep in this recursion you might have wondered shouldn't you know maybe my should get a stack overflow because I'm running a recursive function maybe the stack recursion was too deep no this is actually very shallow recursion but it's very broad because it's branching at two by two at each level and at the bottom which is only a hundred levels deep it's like two to the hundred yeah okay so what if we actually wanted to compute Fibonacci numbers well there's a bonus problem on the problem set where you could find at least a dozen different ways to compute Fibonacci numbers I'll just try one of them I'm only my only purpose in doing this is to show you that I should grab the upper right corner show you that it's perfectly feasible to compute Fibonacci numbers even with values much greater than a hundred using any of these four systems so here I just did the standard trick of exponentiating this matrix and picking the right hand corner yeah maybe you don't believe me that's the hundredth let's just check that it gets off by one okay so I wanted the eleventh one yeah so I would want the hundred and one so there's the hundredth Fibonacci number I could get the thousandth I could get the ten thousandth I could get the hundred thousandth no problem okay okay so with that life lesson out of the way maybe I'll pause for questions yeah in the back excellent question so the question was if you saved all the things you've already computed so that it didn't have to keep computing the same thing over and over again would that make the function more efficient and the answer is yes as you'll see if you do the problem set I mentioned that's actually the very second step is analyze the computational complexity of the program we just ran and then the second part of the problem is analyze the computational complexity if you use what's known as memoization which is precisely what you suggested don't compute something you already know cash every return value and you'll see that that makes it dramatically faster but that's still even with that improvement it's still far from the best way to compute Fibonacci numbers they're much better okay any other questions alright so now I want to take the sort of the opposite perspective so that was an example of a great definition that didn't lend itself necessarily to a good algorithm and this is you know maybe I don't know four or five hundred years I'm not sure exactly when Fibonacci was around but I want to know go now go back much further in history and look at what I would say is still today very definitely should be on the top ten list of all algorithms in computational number theory it's right up there with Euclidean algorithm which came out around the same time but I think in some ways that you know this this the sieve of Eratosthenes is even cooler and even more impressive you probably don't think so because it's probably something you learned in grade school and they're like yeah yeah anybody can do the sieve I know there's lots more sophisticated things one can do with primes but I just want to use the sieve of both as I think a good first nonch less trivial coding example in the Fibonacci also to give credit to Eratosthenes or whoever it was that actually developed the sieve that bears Eratosthenes names because it's quite ingenious and it achieves what even today is an extremely impressive running time on average and that will be something that we'll come back to in some of the later lectures when we talk about computing for example traces of Frobenius or as David Harvey will be talking about in week three computing zeta function in functions and what's known as average polynomial time Eratosthenes beat us to it by 2,000 years by giving an amazingly powerful average polynomial time algorithm for enumerating primes 2,000 years ago okay so how does this algorithm work just to remind you this is one form there are other ways one could construct it but I wanted to just pick what I think is the simplest and would be easy to implement in all these languages but require us to know a little bit more than we needed to know to write the Fibonacci program so we're going to create a vector just think of this if you read a big grid with boxes on it and you're going to cross out primes well each of those boxes contains either or contains across or it doesn't so it's a Boolean value and so we're going to create an array or a vector of n Boolean values our goal is to enumerate the primes up to n and we're initially going to say well we don't know anything let's just assume everything is prime I mean two's primes three's prime everything's prime and back then they probably considered one prime two we don't one is no longer prime it got demoted just like more Pluto okay but so then how does the algorithm work well then we know the ones prime we have to actually put in a special case for that so one is really a special case so we're going to skip over one just leave it set to true because we already know it's prime it's not sorry all but the first yes sorry we set the one to false in the first step I forgot to read my own writing so we set one to false and then for every successive integer p I'm not calling p a prime yet because I don't know it's prime just calling it an integer p is going to go from two to the square root of n and I'm going to check whether s of p the pth element of our vector array is set to true and that means it hasn't been crossed off yet and two hasn't been crossed off yet because we haven't done any crossing off so two's prime and so we're now going to cross off all the multiple proper multiples of two by just you know we could do this by incrementally adding p to p and crossing off all the multiples m between two p and n in this algorithm we're going to cross off every multiple of p greater than p there are many optimizations one can do and they're going to be considered in one of the exercises but let's stick with this because this is the way I've got the coding examples set up okay yeah and in fact we don't need to go all the way up to n either there are many many much more clever things and in fact we could invert the sieve so that we only run over odd numbers or we could implement a wheel or we could implement a recursive wheel to improve the running time there are a million different things we could do I wanted to start with what I think is the absolute bog standard simplest one my guess is that Aristothanes implemented something close to this algorithm okay and then when we're done crossing off all the primes we are a little bit clever about going up to stopping at the square root of n but in fact if we replace the square root of n with n as our bound it wouldn't change the complexity of the algorithm at all it's only going to be a constant factor improvement and then at the end we're going to output everything that didn't get crossed off those are our listed primes and before I jump in to show you some of the code just let's consider appreciate the complexity of this algorithm so what are we doing in this algorithm well we're going to for each prime that we haven't for each number we haven't crossed off and all the ones we haven't crossed off for prime so for each prime we're going to cross off all the multiples of p up to n and we're going to do that by adding p successfully so we're going to do n over p additions and then a crossing off a crossing off actually takes less time than the addition so we could just focus on how long it takes to do an addition if we're adding two numbers that are less than or equal to n well they can all be represented in roughly log n bits a log 2 n bits and so the complexity of adding to n log n bit numbers is o of log n and so we're doing this for each prime p and the number of additions we do for each prime is n over p so we compute the sum of n over p o of log n we get o of n log n log log n and for those who aren't familiar I'll just remind you what the big o notation means it's denoting a set of functions there's a standard abuse of notation going on here that equality doesn't really mean inequality it's asserting something about functions represented by the left-hand side or contained in the set of functions represented on the right-hand side but I think it's fairly intuitive and well established notation so we'll stick with it but you're welcome to read more about it if you want and I put a cautionary note just to remind you that it is not strictly speaking in equality and in particular it is not symmetric so n is o of n squared and also o of n cubed and also o of x to the n these are just upper bounds and it also has an excellent well excellent on average space complexity which is one bit for n to direct to n but not so great complexity if you're thinking about it as a function of n so it's o of n bits as I mentioned in addition to the optimization that Hendrik referred to there are many optimizations one can do the most significant is to use what's known as a recursive wheel which will actually improve the asymptotic complexity by a log n squared factor the space the most important practical improvement in addition to using the recursive wheel is not to actually have your Boolean and rego have n elements but just do them in blocks of n squared at a time you can achieve the same time complexity with much less space complexity but I'll just note that even this bog simple algorithm what would be another algorithm? another algorithm would be just iterate the integers from one to n and just test whether they're prime I mean primality testing is very well studied subject there are lots of interesting and wonderful algorithms out there including a polynomial time algorithm so why wouldn't we just apply our polynomial time algorithm to each prime it's polynomial time in the logarithm of the prime the order polynomial and the number of bits well even if we did that the best the complexity we'd get would be something like n times log n to the sixth we could use a faster algorithm we could be a little bit more a little bit less insistent on a deterministic algorithm we could use instead a Las Vegas algorithm which would give us results that would give us a running time of something on the order of n log cubed n but that's still much much worse than something that is running if we think about the complexity of our sieve it's running time was n log n log log n we're asking what is the average time per prime well there's n over log n primes so if we divide by n over log n we get O of log squared P log log P per prime that's how much time it's spending and that's faster than any primality test if it's known okay alright there we could get close to it using a Monte Carlo test but this is a rigorous algorithm and it's a rigorous deterministic algorithm and if we apply the improvement using what I mentioned before the wheeled sieve we can actually achieve an average complexity that's better than any primality testing known even one that is allowed to use as much randomness as it wants and be wrong 51% of the time okay so this is, you know, so I hope you appreciate the power