 All right, so now we have seen how to measure stuff. Now we are going to get to faster Python, and that should keep us occupied for most of the afternoon. So first thing first, just a little bit of loading here and there. And so the three, let's say, tricks or methods that we're going to use, and I'm going to present there are NumPy, and then we are going to discuss NumBar, and then we are also going to discuss Cython. I'll explain each and what they do in turn. Each of them kind of varies in the sort of difficulties that sort of amount of work that you have to kind of put in to make them work, and each of them sort of also can yield a different level of performance, of performance improvement, depending on the specific problem you want to apply them on, okay? So we'll also try and discuss that a little bit. And of course, this should not dispense you to just apply good sense on your data. Try to think a little bit about the sort of operation, try not to be too redundant, maybe try and think about the complexity of the function that you have and so on and so forth. All right, so without further ado, first with NumPy, we've seen already a little bit NumPy this morning, and we have used it a little bit. Just maybe as, just to give me an idea, could you please put a little green tick, yes, if you are already familiar with NumPy. And by this I mean that you already feel quite at ease using it on, you know, on your project and so on and so forth. All right, and maybe a little Red Cross if you've never heard about it, or only maybe this morning. Okay, so it seems that maybe at least about half of you have already heard about it enough and are quite familiar with it. So then I'm going to go fairly fast then through the coming part. But the core idea under NumPy is that it uses a number of structures and function that are written in C++ under the hood and that do what we call vectorize operation that means that you do operation at the level of all sets of numbers, arrays, rather than number by number. And this, you know, it goes much faster then. So the basic NumPy type is the array, okay, which you can create from any container, such as a list, for instance, and or from some pre-existing function. We've seen this morning already to create an array, create a 2D array with only zeros, you have five by five, okay. Or you can create a bunch of random numbers like this, okay, 10 random numbers there, okay. And then of course we can fairly easily see and check that doing one operation on a, you know, on a basic list, like for instance, multiplying all elements by the team is much longer in Python than using NumPy arrays, all right. Also the way to write it is much simpler. So two gains there, right. And also one thing is that array, you can multiply them easily by one value, but you can also multiply array or subtract array to one another. That's what we have used with our little distance example where I subtract one point and the other in one go, all right. And so we can do that with a little bit like all of our little types of function that exists, like here I just do a sum, then a mean, then a standard deviation. I even have to kind of compute a little standard, sorry, I have to create a standard deviation function there and also sort it, all right. So what we see here is that each time the NumPy implementation is much, much faster, but this difference is not always exactly the same, okay. So the factor by which you will speed up is not always the same, all right. So that's kind of the super quick guide there. So basically the 101, like the take home message on that is just like whenever you have operation on a single letter type on a whole like bunch of values, use NumPy arrays, it will be much easier to write your code but also much faster. You have here this little link to their absolute beginner's guide, which is quite nice starting point to really get started there. Now there is a question by Tess asking, the question is NumPy faster than pandas. Now pandas is actually based off of NumPy in the sense that a pandas column, a pandas series, okay, is actually under the hood in NumPy array. So most operations should have quite similar performance, okay. Maybe sometime for some case, some use cases, the pandas array might be slightly slower because they do a few additional check that NumPy doesn't do, but that would be the exception rather than the rule. All right, so now we have seen already together that we have rewritten our base Python function from basic Python to NumPy, okay. So it's very, very similar, it's just that now rather than treating each measurement in each point separately, we just treat point as one dimensional vector there. So we can directly subtract, square some and then do the square root in very simple call. So you can see that both like the code is a bit, let's say simpler once you know what these do, okay. But that we have seen also together, they are much, much faster. And then we come to our first exercise there, okay. So just to get you also to apply this sort of stuff. So for those of you who are quite familiar with NumPy, it should be fast as well. So if we go here, all right, now your goal, okay, is to take this little function there, which is in native Python. And now to try and rewrite it in NumPy to make it faster. All right, let's try and see how we can solve this one. So the first thing is that I want to apply this function, but now I wanted to be with NumPy elements. So for that, I see what I do here. I create the X, okay. And then I go for each element, I create n elements, okay. And then these n elements, I compute there where they are. So the X here and then I compute X square minus X and I add that to a running sum. Right, so where we can start to go is to say, all right, we have there n elements, okay. And these n elements will take values between A and B by increments of DX, all right. So maybe this is where we can use our hint, where we create something like this, okay. So that creates all of our element. It could be a way to just have here something in our for loop instead of doing this computation there, we would just do the average. I mean, we can just try that. That would be maybe minimal when it comes to the changes that we do there. But, you know, you never know. I said to go so np.arrange. And let's call that maybe just numpy one. And then we go from A to B by increments of DX. Okay. And we can then try maybe this and then we have increment native. Numpy one, all right. And so we can try this, see if we get something out of that. Okay. So looks like it's taking a bit of time. Let me check. I know I've made a mistake, sorry. So that would be actually directly X there. That's better. Sorry, I was a bit eager. Okay. Looks like we have actually lost some time there. It actually takes us longer. So maybe not the correct way to go. One thing that we want to test also is that we want to check that we get the same result because even if you get something like this, okay, you want to make sure that you get the same result because okay, maybe even if you get something much faster, if it doesn't give the same result, it's useless to you. So it's also a good thing to just check you compute both. And here you see that you get the same result. So it's a relief, at least, you know, you are not faster, but at least you get the same result. So you got kind of the process, right? Now for tests, can you explain why it is X? So here, what we see is that in this function, we go from A to B by increments of DX, all right? So X is actually computed using that. Now what we do is that rather than say these two line, we can just write NP.arrange from A to B by increments of DX. So that's, for instance, what we get when we say form zero to two by increments of, I don't know, 0.01. And then we get all of our Xs, right? And so we can iterate through that. And by iterating through that, we get directly the X and we don't have to do the modification ourselves. But now this has not given us much better results. So we can come back then to what I mentioned a little bit in the chat, a couple of time, is that NumPy reshines when you have, because you are able to apply operation on whole vector at once, I mean that for instance, if I have, I know X is equal from zero by two by increments of 0.1, I have EIX. And I can fairly easily write, for example, X times three, and it will multiply everything by three, okay? So for instance, there is nothing preventing me from computing then the square. And then from there, you can sort of see where I'm going because I can just do X squared minus X. And I then just compute this quantity that is interesting towards there, all right? So from there, we can start converging toward a little solution where we would just, for instance, have our function there, okay? And then we, instead of having everything there in a for loop, we would just compute DX, then we get X is equal to NP.arrange from A to B by increments of DX. And then we have the X squared minus X, okay? Which gives us some sort of the function element, let's call it F for instance, and then we can have F dot sum, okay? So we sum the element of X, and we can then just return that because that looks like this is what we want. So far so good? Yes, okay, now of course, first thing we want to do before even timing it is checking that we do get the same result. See here, for example, what I get, I expected to have that, what I get is this. So what is likely there is that I forgot to, yeah, here I forgot to multiply by DX at the end. So I can just do this either here or before there, but here it suffices. So now if I rerun this, now you see that I do get here the expected result up to a small numerical increments. Let's check here, S equals zero, A to B to DX. Then you sum squared, okay? So that might be in the numerical errors there, okay? But that's then the first idea where you kind of get your result. So then we can sort of jump to the solution. We can see that it's kind of the same thing. It's just that now I even don't even use the F intermediate. I just go directly and I compute X squared minus X sum and then times the X, okay? And then I do both and compute the two and then you measure the time it takes to do the two and you will see that you get something better, okay? So there you see that now I've gotten something nicer there. So because I have this same result and that I've gotten a speedup of 7.8 in that particular case which is a bit lower than what I had when I did my testing first. Those are people that have a higher factor she can tell me if they have done things differently or if it's just right now, my computer was a bit slow-ish in finding the solution because this happened sometime, especially since my computer is doing the recording. This for me, I tried this solution and I also get 20X. Now I get a faster, a higher speedup. Yeah, I get like within five minutes. Ah, so maybe it's just my computer. Yeah, you see, it's a bit unstable there. All right, let's gear up maybe. That's that, yeah, let's do like maybe 30 run. Just I think, yeah. Five, take a bit more time to compute. I guess it's too harvesting your personal data and the background that is showing. It might be that, yeah. I think it's this mostly because when I did that outside of the Zoom I got a much better performance. But so yeah, all right. So there you have it. So the solution is kind of to rethink the way that we do our for loop, okay? And so we don't do for loop, we just do the operation directly on the whole vector and then sum all the elements in the vector and that gets us our result. Now, is it clear for like, is it clear what we did and how we did that with NumPy, the process in general? Yeah. Now, given what we have all done up to now can you give me a, you know, a weakness of my method? Where we did become bad or risky or something. Okay, so then I test n can be zero. That's completely correct indeed. If n is not zero, then I get a mistake because I try to divide by zero. Then York, you have raised n. Yeah, if I do a lot of steps, so if n gets very high then everything needs to be in memory. So it's memory intensive. Exactly. Perfectly right, indeed. So here with one million elements, I will need something like seven megabytes. So it's basically not much, but if we imagine that we do something with 10 to the power of 30 or 55 or something like this, especially if it becomes maybe a multi-dimensional function, it can climb it very high. And so you could see that here, this x there will not fit in memory anymore. And so you might start to see problem. For this simple example, it will come only for very, very high number still. That's something that it's nice to just pick up like now, okay, we have exchanged a time constraint for maybe a RAM constraint. So in this sort of case, then what one could do is just to say, okay, maybe you can then block your ends so that you can, if you see that if n is above, I don't know, 10 to the power 20, then you operate by block of 10 to the power nine or something like this, okay? And so you can just chain different code to the integrate numpy function fairly easily. And then of course it will take slightly longer, but at least you stay within the RAM constraint that you want to have, right? Is this clear to everyone this little process there? And how we would circumvent the RAM problem? Yes. All right, so something to keep in mind. Yerk, you still have your end raised, but I guess this is just because you forgot to lower it. Yeah. Now, there is a question by Tess, and so she asked if I can provide the code for that. Now, the good thing is that I don't have to because you already have it. So if you've tried it a little, you can see here that you, I have a Jupyter magic command there. So if you actually unzip that thing and then play it, then it will load what is already normally in your solution folder. And you have here the solution file to all the exercise and also for the micro exercises with some commands and so on so forth, right? So normally you have everything ready. But if you want to have like the very precise one which I've put it there, just put it in the chat and I will paste it, but it's just the same thing as this basically. I just meant the one where you circumvent the RAM issue. Ah, I have not programmed that. Sorry. In other words, just curious. Ah, yeah. The programming for that would not be that hard. You would do, yeah, so you would do for instance like if and above like a threshold and then you would say, okay, so you say you have for instance your threshold, maybe it's 10 to the power nine or the maximum block that you want to allow. Then you would have N divided by your threshold would be the number of blocks. So it would be slightly involved to program, okay? So I'm going to do an incomplete solution like pseudo code style. And then the idea would be to say for block in and be on a number block. Okay, so A starts at A and then B is equal to the size of a block. So that's threshold times DX. So then you have like, let's not call it B then because it will just be block, all right? And so then what you would do is maybe you would integrate between A plus, so it would be range here. So we go from A plus the number of point per block so which is threshold times which block we are currently doing. And then here for B, it would be so it would still be A, but plus and then for the here next block, all right? And then you don't do that for N but for threshold points, all right? So that would be sort of here the idea that would be underneath that. And then of course you would have something like the running sum, okay? And then the running sum gets incremented as we go. And at the end we return S, okay? Right, so that would be the core idea. Now for the precise implementation, I would have to say, okay, here we have to be careful if N not divisible exactly. Okay, and then you have here maybe a border effect. Don't count the same number twice. So you have to be careful about that, okay? So that's maybe two point which are, I would say a bit tedious. It's not like hard, but it's tedious to do. So I won't do that because that's not the subject here but that's all the kind of bare-bone shape that this code would take, all right? So it needs to be refined, but that's the beginning. There you go. Thanks. Okay, now if there are no more questions, we can then continue on. So that was NumPy, okay? And so in NumPy, it really shines where you have an operation and you want to do the same operation on plenty of numbers so that you can vectorize everything. Now let's go.