 In the back, can you hear me? Yeah, OK. So this is, I guess, a three lecture series on scientific computing with Python. And it's going to start very basic, and then we'll get progressively more scientific. But it's going to start really just with Python. And it's supposed to be interactive. So I'm going to do a couple polls before we start. So how many people have a device, a computer, an iPad, a phone? It'd be kind of hard, but you could do it with them, with which they can connect to the network right now? OK, how many people don't? Anybody? One? OK, well, if you don't have it, you won't be able to do the interactive stuff. So you could try to borrow one or look on with somebody else, but it looks like most people do. That's good. That's step one. Now, before we actually start getting everybody connected, though, how many people already program in Python? Wow, OK, how many people have never used Python at all? And how many people of those, how many of you have never programmed at all? One. OK, so good. That wasn't supposed to be a shaming moment in the class. You look like you don't mind. Oh, I'm a theorist. Come on, even the greatest theorists of our time built their own computers back in the day. Well, it seems like this lecture may be, for many of you then, a little bit basic, because today is going to be really just introduction to Python, a couple of high-level words about why I think it's a good language for scientific computing. And then just like, what's the basic syntax? How do you use Jupyter Notebooks? How do you do some very basic things and use the basic data structures and flow control? And I apologize if you already know this. If you have insightful, guru-level comments to make, to provide us with, you're welcome to do so when I'm not here, and impress your friends that way. That's fine. I think this is geared to a level of mostly people who've programmed, but maybe don't know Python. Hopefully you'll be able to absorb everything you need to know about the basics of the language in one lecture. Tomorrow, we'll do a tour of some basic functionality of NumPy and SciPy, which are numerical packages. And that'll end with doing some sparse matrix calculations in class. And the last lecture, we'll talk about sort of principles of how to optimize a scientific code. And we'll do it with Ising Monte Carlo as a running example. So that's sort of the plan for the three lectures. It's supposed to be interactive. So to get the interactive going, I want everybody to open your browser. Hopefully it will work in any modern browser. And you can go to the website, type into the address bar. HTTPS. Here, I'll even write it. Let's see if I can do this. Jupyter.ictp.it. Now, you all can read that, right? Here, let's make that a little bit bigger. How about that? I think I spelled it right. Don't miss the S. It won't connect if you don't use secure HTTP. This is a server that we set up. It has not been stress tested for 180 simultaneous users. So everything might explode right now. Hopefully, once you've gone there, you see this page. Is it working? Is it not working? Amazing. OK, now you need to log in with the ICTP credentials that were given with your ID. Mine, oh my god, you can see the blocks of my password. You count really carefully. You'll be able to figure out that it's the same length as all of yours. Now comes the moment of truth. As the poor server simultaneously creates 180 user accounts. Let's see if it works. Yeah, if the computer crashes, then, well, it won't be interactive, will it? This is not very promising. Has anybody seen more than this yet? Oh. Your server is starting up. Good. Mine is not. Great. So most of you won't have the stuff in there, but has everybody gotten to a screen that looks kind of like this? OK. Wait, I should do hands again. Is there going to be like a wave? Put up your hand if you've gotten there. OK. It's struggling, our poor server. It should not matter if you're on EG Rome or ICTP secure or ICTP open. It's an internet-facing server. Yeah, but then I have to distribute the notebook. And this is much better. I mean, some of you, it's true. We could do that, but let me start talking, actually. I can start going, and I'm going to tell you what to do. And for the first bit anyway, I'm just going to blah, blah, blah, so it doesn't really matter if you've loaded it. Hopefully, by the time we get to an interactive bit, everybody's will have loaded. So what you're going to do once you get there is you click on this Examples tab. You should see, hopefully, this Intro 2018 ICTP notebook. And then you go to Use, like that, and then Fetch a Copy. And if we're very lucky, you'll see this. And that will then be your very own copy of this beautiful notebook, which I wrote just for you guys. OK. Don't you feel special? So we're going to work through it. This is actually Jupyter. That's what it says up top. And it's become a sort of standard interface for doing interactive work in Python. And that's, you guys are all logging into Jupyter clients of your own running on a server. It's kind of an interface a little bit like a Mathematica notebook. You have a mixture of code and comments and output from calculations all in one thing, which you can save and which you access to your browser, even if you're running it locally. We didn't have you set it up locally because it's a lot easier just to get going with everybody on the same server. So we'll explain more how to use it as we go. What is this course? So it is a short crash course on Python, computing with Python, scientific computing with Python, my flavor of computing with Python. As I point out at the top here, and I have a few links, if you really want to learn Python, you should just sit down for a day or two and work through some of the tutorials available on the web. I've got links to some good ones, which have a scientific flavor. Some of them are longer, some of them are shorter. One of the reasons that Python is a great language to work with is that it is very widely used. So it's kind of circular. You use it because it's used, right? But that's actually a really good reason. It means that there is a lot of documentation. Google is your friend. You will find anything you want to do with a little bit of searching. And you can find your feet that way. And there's also lots and lots of libraries available. So let's just sort of talk through that a little bit. So why Python? This is my soapbox period. So why Python for scientific computing, right? So it's a simple, well-structured general-purpose language. It's very readable. Unlike many other languages, white space matters in Python. So basically, how many of you know about, what is it, the obfuscated code competitions? Has anybody submitted anything? Is this too old for you guys? Don't they still run? Does anybody know what obfuscated code? So you write a sequence of symbols that turn out to be a C program when you squint at them. And they do something really miraculous, but you have no idea when you look at it what the hell it was supposed to do. And there are competitions for who can write the most miraculous programs in complete gibberish. But this is very bad for doing science. Why? Because you finish your miracle late-night work and you get this cool program. And then you come back to it a couple weeks later, and you're like, what, what, what? And forget about it a few months or a year later. That's you. And if you send it to anybody else, they don't know what you did anyway. So nothing's reproducible. And you can't really understand what you did. So Python kind of forces reasonably good habits on you and how you write your code to make it more readable, make it long-term, maintainable, understandable, debuggable. It was actually invented by Guido van Rossem as a teaching language. And that's why it has that kind of structure built into it. It's a high-level language. So you can do high-level things with just a few lines of code that should help you rapidly develop or explore a problem. You don't have to write lots of code just to tweak parameters or play with different ways of looking at a model or something like that, different ways of doing a calculation because it can do a lot of that for you. It's interactive, as we'll see. So again, that makes it faster to explore things. There's no need to compile, run, debug, revise, compile, which is the standard protocol for compiled languages. And you can do your data collection or if you're doing numerics generation, analysis, and publication plotting all in the scientific Python world, which makes it very convenient to go from exploration to production collection to paper figures, right? Which is, of course, the only thing we actually produce around here. So then, in terms of speed, some of you will sniff and go, ah, but it's an interpreted language. How could it possibly be fast enough for the hardcore numerical computing that I wanna do? And the answer is, it usually is. It is not always. But for most purposes, it will be plenty fast. And the main thing to remember, and we'll discuss this a lot more in the third lecture particularly, is that your exploration time, your development time is way more important than CPU time. So getting the right ideas and the right calculations and the right algorithms in place, which you can do within Python more rapidly, is more important than getting an extra factor of five or something from the compiled code. And usually, if you really need to, you can swap out sort of the bottleneck code for a compiled language when you get to that stage in a project anyway, it's not hard to do. So that's why speed isn't usually a problem. I already mentioned there's a vibrant community, so lots of online documentation. It's open source, you don't have to pay for it. Steve Wolfram isn't getting your money. And I mean, he's a great guy, but he has a lot of money. And then there's an extremely rich range of scientific computing libraries and toolkits which allow you to do all kinds of things without reinventing the wheel. Now, when you are a graduate student particularly or whatever you're a student and you are first playing with doing some kind of calculation, actually reinventing the wheel is an extremely useful thing to do because then you understand how the wheel works. But once you get past the basic wheels, you don't really usually want to use that version. It's much better to just go ahead and use mature versions of whatever kind of algorithm, fast Fourier transfer, whatever it is that are available in mature toolkits. So there's a learning aspect while you're exploring and you want to reinvent the wheel, that's okay. And you can do that in Python and then you can just swap out your kind of, you know, your pedagogical stuff with actually getting stuff done with mature libraries. So that's my two bits on that. So I had an argument, argument yesterday over dinner over what constituted pure Python. And the answer, there's two answers. There's what's the core sort of functionality of the language that you would get if all you did was install Python and start programming and using the basic data types. And actually that's most of what we'll talk about today. But I think that it is perfectly fair to say that any of the would have now become very mature standard scientific libraries are pure Python from our point of view. There's no point in making a distinction. If you can use the toolkit and it's mature and reliable, you should use the toolkit. And in particular, these are the main components that you use in a scientific setting. So the core pieces of the platform are Python itself, which is the language interpreter. It has some standard data types, we'll go through them today. Python is under development, so there are versions. Python 3.7 is current, use it. There's an old version, Python 2.7, which is slightly incompatible. It is almost 10 years old and will no longer be maintained in 2020. So don't use it. There's no reason to use the older version. Then there's this Jupyter, which is what we're using here. This notebook-based interface. I'm a big fan of these. I actually do research with Jupyter notebooks all the time, like stuff that I'm working on that will turn into real science. Because it gives you a nice framework to keep track of code and output and comments and everything in one place. So that builds on something called IPython, which is just the interactive Python shell. This interface gives you interactive manipulation of plots. We won't talk about it, but it's easy to use basic parallelization if you have a bunch of computer cores available and you want to farm out some computation. You can do that using notebooks and Python pretty easily. Lots of use for extra bells and whistles. So then the three key libraries are NumPy, which is kind of the baseline for doing scientific computing. It is a library which provides numerical array objects and routines to manipulate them. And some basic linear algebra. And then SciPy, which is scientific Python, as opposed to numeric Python, it's a set of libraries built on top of NumPy, which do provide a lot more sophisticated scientific tools. Signal processing, numerical function optimization, ODE integration, all kinds of things that you might want to do are provided by SciPy libraries. Then plotting, the primary plotting library, there's some other ones, but the primary one, which is what I use, it works very well, is very mature, is called Matplotlib. It does really publication-ready 2D plots, they're beautiful. And you can do basic 3D stuff as well, that's getting better. There's some other 3D plotting libraries, if you're into 3D plotting, that you would want to investigate. And then I'm mentioning, we're not going to use it at all in the course, but these last two, pandas and MayaVI. MayaVI is one of the other 3D visualization tools. If you need to do it and do interactive things in 3D, then MayaVI is something to look into. And pandas is a library that's relatively new, which provides a layer of data structures above NumPy again, which are richer and they're quite useful for doing sort of analysis of heterogeneous data, relational data, and things like that. It's kind of like a data structure that provides you something like an Excel spreadsheet, programmatically. So that we won't use in this course, but actually I find it sometimes useful to use as well. So I've talked for a few minutes. How many people have gotten to the notebook page? Ah, good. Has anybody not gotten to the notebook page? One, two, three, four, five. It's just stuck. What's that? It's loading still. You can try to reload and see if it goes. It's still okay, I can talk a little bit more. So hopefully you guys will come through. So, but at this point, we're getting close to where you actually will want to be able to do stuff, right? So there are two primary ways. This seems like crazy. I'm like telling you how to do workflow. I feel like some like business consultant or something like that. But you know what? Let's be practical. Let's just get stuff done here. So anyway, there's two primary workflows for working with Python. One of them is work in a Jupyter notebook, write code and cells, analyze plot, et cetera. Everything in that case is stored in a .ipinb or ipython notebook file, which is what we are looking at. We are looking at an ipinb file. Or the kind of more traditional way to do things, before this interface, was write your code in .py files using a text editor and run those either from the shell or within, or actually within the ipython notebook, you can do that as well. Or some other terminal and output files with your data and things like that. And we're not gonna do that, but you can. And if you're doing things that actually get programmatic and you wanna push onto clusters, then you'll have to start tying your code up in .python files, okay? So, we'll stick to the first. Well, I guess I'm kind of just reading my notes here. So, I'll come over here. I can read it. While you're using a notebook, there is a kernel running, which actually executes your commands and stores your variables. So, that's a bit like Mathematica. You can quit and restart the kernel and it will forget everything it had but anything that's visually displayed in the notebook will still be there until you overwrite it, okay? And saveable. So, that can be done from this kernel menu. You see restart, restarting clear output, restart and run all. These are things that will kill the kernel in the background, which is storing whatever you've told it to store, okay? Resetting things is often quite useful. You can check that your code actually is doing what you want. If you go back and forth in the development process, you might have set things later than you needed them. And so, it's useful to go back and make sure you're actually calculating what you think you're calculating. Or if things just get screwed up, sometimes it's useful. Okay. This footnote is not so important. .py files are called scripts. If they consist primarily of sequence of commands to be run and modules if they're basically libraries containing a bunch of functions that you wanna import and use in something else. So, module is the Pythonic term for a library of functions that you can call, okay? Okay. Notebook usage. Now, how many people have used VI? How many people actually like using VI? It's okay. If you don't know VI, that's good. You'll be happier for it. So, in the bad old days when there were no things like the web, you had a terminal that you had to do all your editing in, you did command line editing and to do editing of a text document, you usually had two modes. You had a mode where you could actually edit the text and a mode where you would issue commands which were things like find this or replace this or add a line there, that kind of thing. So, it's an edit mode and command mode. VI has an edit mode and command mode. It actually has been around since those bad old days with apologies to the VI aficionados here. I actually use it sometimes. The notebooks actually also have a command mode and edit mode and right now, unless you guys have hit keys, I think you will be in the command mode. So, in particular, if I press H, it didn't type an H into a box. It popped up a help panel, right? And this tells me keyboard shortcuts that I can use mostly in command mode. Right? And I can scroll and look around and there's all kinds of things here. Great. Command mode is useful. That's how you move around and interact with cells as blocks. You can delete them. You can copy, paste, whatever. You can move them around. You can use shift enter to execute them. I'm gonna execute this cell. It didn't do anything because it was just a comment anyway. But I did it and if I'm in edit and I can use enter to go into edit mode and now I can edit my text which it was rendering for me. And I could say press escape to go to command mode if you really want to, like that. And then execute and it re-rendered it. And where'd it go? Press escape. There it is, right? Here, I'm gonna use this pointer they gave me. If you really want to is what I just typed there. So I'm belaboring this because it's a pain until you get it, right? Once you know there's a command mode in edit mode then working with these notebooks is very fast. If you get confused about that then you will be confused working with the notebooks. Okay. In editing mode, well, okay. So there's shift enter always executes the current cell. There's actually two kinds before I say what these are about. There's two types of cells. There's what are called markdown cells for notes. And you can see up here this dropdown tells me it's a markdown cell. And that's like this one which was just full of comments. And then there's code cells which are the things you can execute. And if you're in a code cell in editing mode then pressing tab will invoke autocomplete. Pressing shift tab will bring up help. Every object, function, whatever in Python has help documentation attached to it which is interactively available. This is part of why it is a good language just to get going with, okay? So I'm belaboring it but shift tab and tab are your friends. You want to use them, okay? So anyway, now we have the first official exercise. Try editing this markdown block to make it more interesting. Go! Who can make it the most interesting? The one that says this one. I don't hear a lot of clicking. Modern keyboards are kind of mushy. Is that what this is about? Okay, I'm gonna make it more interesting. Help your neighbors if there's problems. So, did everybody make it more interesting and then manage to shift enter in order to re-render it? Okay, very good. So, some of you will see that one thing that it supports which is useful scientifically is that in markdown blocks you can use math symbols from latex just for notes to yourself. And it is exactly latex syntax. So if I put in these dollars and dollars, dollars blocks like that they will render nicely. So this is useful for making notes to myself or making a notebook that I could share with collaborators or something like that, which explains some analysis, does some computation, does some plots, all in one place, okay? So now the next exercise, execute the next block, then create a new block, type x.press tab and shift tab and just see what happens. We give everybody a minute to do it. We executed it. I'm creating a new block by pressing A. Oops. Ah, so you can create a new block by pressing A from command mode or B. A will create it above where you are currently pointing and B will create it below. So if I create A, then I hit enter to go into it. I press x.tab, blah. And that's the autocomplete. So this symbol x, which is a variable containing the number 10, has a bunch of methods on it and autocomplete knows what they are, okay? So tab, very useful. You can investigate what you are playing with. And if I hit shift tab, and I hit it again and I hit it a third time, I will get a help block. So this is giving me lots of information about that symbol x. It's an int, its string form is 10, and the, okay, if I call these int functions, it will convert a number of string to an integer. I have, it tells me how to do other bases if I wanna represent things in binary 0B100, et cetera. So, and I can close this by clicking on the little x. These two things, shift tab and tab, very useful, memorize them, okay? Again, this is part of why Python is nice to work with. Because everything is available and there's documentation everywhere, okay? Now, let's run this stuff. So, what's gonna happen? I know for some of you, this is like really kindergarten stuff and for some of you it's kind of new, but what's gonna happen? Hello, world! Oh, it printed it. And what about this? It's the same. There's a small difference. One of them it, oh, wrong button. One of them it printed, that's output. One of them it's actually just showing me the value of this string, which was the last thing in the cell, okay? So that's a string, 2.5 times three. Bunch of scientists here and you guys can't multiply. It's embarrassing. Three to the third, 27. Three plus three, six. What's AB plus CD do? We're using it as a calculator. If you get nothing else from this, you'll know how to use Jupyter Notebooks as a very heavy-duty calculator. So what does this do? What's it gonna output? Plus, one applied to numbers will add them and one applied to strings that will concatenate them to make bigger strings, okay? Easy. And finally, just a note that Python doesn't, those two strings are equal even though I use different quotes. The quotes introduce the string, the fact that it's single or double doesn't matter, okay? Okay. Now, variables and objects. Maybe we're getting somewhere interesting. Everything in memory in Python is an object. Every object has a type, such as integer, string, nd array for NumPy arrays. Variables reference objects. They can reference objects of any type and that type can change, okay? So it's not like a typed language like C where you have to say that some variable is a care or an int or something like that and then for the duration of the program it always refers to a care or an int. A variable in Python can refer to anything. So here, for maybe the one person who doesn't program, equals doesn't mean equals in programming, just as a general rule. It means assign the thing on the right to the thing on the left, okay? And so A equals three, assigns the integer three to A which we can see if we just type A and look at the output. That has a type int, A plus A is six, two plus A is five and now, without declaring anything, I can just assign an array to A, oops, ha! The first bug in my notebook. I haven't actually imported the NumPy libraries. So, fine. So everybody, create a new cell and then we will do the lazy way of doing this, percent pi lab inline. I wasn't gonna have any actual scientific stuff until the next lecture but I put this in here and forgot. So if I do that, then that basically loads the NumPy and Matplotlib namespaces and we'll make an array available to me. So we'll talk about that more tomorrow. But now I can use it. So A is not an array, the type of A is an nd array, A dot shape, what does that mean? What do you think the shape of A is? It says two comma. Yeah, so the shape of the array, an array is actually an arbitrarily many dimensional rectangular grid of numbers. In this case, it is just a one dimensional thing so it only has a length and that's two. So two is the shape, two comma. A plus A, it added these two row vectors, A and A to itself, gave me two four. Two plus A, what did that do? Yes, it did do broadcasting but let's leave that aside for the moment, it added two to each element. You're right. Good, and now I can go ahead and say A is hello world, it's a string, and the server goes down in fiery mess. Yes, okay, so what's the type of A? It's a straw, A plus A, hello world, hello world because plus or strings concatenates. What about two plus A, what's that gonna do? It's wrong, what's that? It's gonna blow the server up. Okay, let's see. Well, I don't think it blew the server up but it indeed threw a type error and it said basically I can't add a string and an integer, that doesn't make any sense, and Python gave me a nice descriptive error message. Don't do that because of this, right? Okay, good. So just a comment there, this is called the operator overloading which is that the plus symbol is an operator and it can mean different things depending on what you try to apply it to. In general in Python, operators and functions will try to execute no matter what type of objects are passed to them but they might do different things depending on the types like plus adds numbers and concatenates strings and if there's really no sensible way to interpret addition then it'll throw an error and say you can't do that. I don't know how to interpret that overload, okay? Okay, so now this is an important thing in Python compared to many other languages for those of you who are familiar with them. All variables in Python are references to the objects they contain. Assignment does not make copies, okay? So what does that mean? So here I'm creating an array A and now I'm setting a variable B to refer to the same array, one, two. So B is array one, two. And now in this next box this is gonna set the first element of the array to zero. Also all arrays are zero indexed, all lists are zero indexed in Python. So let's take a look, there it is. It set the first element of the array to zero. So what is this gonna do? What's it gonna display? So who thinks it's gonna be one, two? And who thinks it's gonna be zero, two? Okay, wait, let me try that again. Who thinks it's gonna be one, two? Who thinks it's gonna be zero, two? Who thinks not at all? Because that was a sizable fraction of the room judging by the response to that question. That was a dichotomy, there's two possible outcomes and you have to come down somewhere or else the fence will hurt when you land on it. So, ah, it scrolled. No, it didn't execute, okay, here we go. Dee dee dee dee dee dee dee dee dee dee dee dee dee dee dee. Dee dee. Dee dee dee dee dee dee dee dee dee dee dee dee dee dee. Wow, it's thinking really hard, what is going on? That is strange. That is the third option, good point. I retract my insults. What is it doing? Okay. Well, so it should be zero two. Maybe it will respond by interrupting the kernel and doing that again. Did my kernel crash? Ah, there it is. Okay. Array zero two. So what happened? The point is that in this code, this is creating an object in memory, which is that array. And it's just assigning a reference to that array here. This is assigning the reference to the same array, which means that if I change it here, it's changed in the original place. There's only one array, one, two. They just, two variables refer to the same thing. That is true of every variable in Python. So in some programming languages, you can have what's called, well, you can have variables that are references or pointers or kind of analogous, or you can have variables which just are the thing. And if you have an assignment, it makes a copy. That does not exist in Python. Everything is a reference. And once you wrap your head about that, it means you don't have to remember that there's two possible ways it could behave. There's only one, yeah? Now, how do you copy? So you can do C equals A, I think A dot copy. And we'll take a look. There's C. And then C one is equal to seven. And C is zero seven. But A is still zero two. There is a mechanism to copy, but you have to do it intentionally. Which actually is often, if you think about it, what you want. You don't want the thing to copy, especially if the object you're playing with is very big. So in a lot of computations, if you like doing quantum mechanical computations with exact ionization, you end up with some very big matrices, very big vectors. You don't want to make unnecessary copies because you might be memory limited, okay? Yes, so Python handles memory management. The way it does so is by a method called reference count garbage collection. So if, I can't really demonstrate this, I can just tell you what it's doing. If you have this array one two currently has two references to it, if I were to forget the references, I can delete the variables. Or they go out of scope. Dell A, Dell B. Now I can't refer to that array I created anymore. They're gone. And the array object, every object in Python actually has a reference count. So when I did that, the reference count went from two to one to zero. When the reference count goes to zero, Python knows the code can't refer to the object anymore and it will re-deallocate it. So it does automatic memory management. Does anybody see the problem with that method? There is a, I mean, it's a very simple method. It works pretty well in practice, but it is possible to have big memory leaks. Circular references. So if you create a structure where there's a variable which has some field which refers to another object, which refers to another object which eventually refers back to itself and then you forget any reference to that ring of objects, each of them still has a reference count from the one that pointed to them. And then it won't get collected. So if you're doing code which has kind of lots of references and kind of a graph structure, you need to be careful that you don't create loops if you wanna deallocate. If you're never gonna deallocate, it doesn't matter. Any other questions about that? Since we're diving into some more technical details. Sorry, say it again. The reference thing is done so that you as the programmer don't need to think about memory management unless you really have to. That's right. It's trying to automate the allocation and deallocation of memory, which in many languages is done manually. Okay, so let's move on. Moving right along. What are the types of objects? So the basic types that are built into Python are various numeric types, integers, floating point numbers, like 1.2, complex numbers. It's a built-in type in Python, quite convenient. Unfortunately, I guess they were engineers or something because the square root of minus one is a J. Not an I, but there it is. If you write one J, actually another important notational point, you have to have, it won't recognize, if you just wrote a J, it would recognize that as like the variable J. You have to have a number before it that ends in J and then it knows it's supposed to interpret as a square root of minus one. It's an imaginary number. So those complex numbers are built in. Boolean is a built-in type, true and false. Strings and quotes, like high. Then there are a bunch of composite types which are actually basic types in the Python language. So there's something called a tuple, which is written with parentheses and has objects separated by commas. The things in there can be any kind of Python object, so two, seven high is okay. It's an ordered collection of those objects. And after you create it, you can't change the items you put into it. So I couldn't, after creating a tuple, change that seven into a six. I'd have to create a new tuple. So that's what's meant by this comment here that it's immutable, some obscure term, but that's what it means. I can't mutate it. Lists look a lot like tuples. They have square braces instead of parentheses to create them. Again, they can contain any kind of Python object. It's an ordered collection of objects represented by square brackets. And it's mutable. That means you can add, remove, change, do whatever you want to the list after you create it. You can append things to the end. You can change elements in the middle of it, et cetera. Then there's a basic type, which is called a dictionary, also known as an associate of array, which if you think of a list as being a thing which maps an integer to an object, like this one takes the integer three and maps it to the string high, dictionaries take whatever thing you want and map it to another object. So there's a set of keys. In this case, the key high is a string and they have values three, which can be any other object. That's a basic type in Python that allows you to build complicated composite data structures just from these sort of simple associate of arrays, dictionaries. We're gonna go through these in more detail in a minute. And functions are actually objects in Python. So you can pass functions around like any other kind of data type. And then you can call them even though they were pasted and so on. So that's also something not supported in most languages of an earlier vintage. It allows you to do some amount of what's called functional programming. We'll go through this in more detail today. The common scientific types which we'll get to in more detail tomorrow are NumPy arrays. They are just like lists in that they are ordered collections, but all entries have to have the same type. So a NumPy array is an array of floats or it is an array of integers or something like that. And that allows them to be much faster than the general purpose lists. And that's what you're gonna wanna do most of your scientific computing with. Sparse arrays, which are arrays which have a lot of zeros in them are represented by all kinds of special types. This pandas data frame I mentioned is a high level table, a bit like an Excel spreadsheet. Okay, I could make a much longer list of scientific types. The most common are the arrays. These are used everywhere. Everything else is a little bit more application specific. So let's keep going. We are going to walk through the numeric types. As I said, there are four integers floats, complex Boolean. Let's go through. If I set A equals four and I look at the type, it's an integer. If I test C equals four dot, it's a float. If I make something with that complex J, it's a complex 1.5 plus 0.1 J. The real part is accessible, A dot real. The imaginary part is accessible, 0.1. So the imaginary part is a normal float. If I make this flag three greater than four, three greater than four is a comparison. It's going to return true or false, and it's false. The type is bool, type of true is bool, right? And I can force a particular type by, in this case, that one is an integer, but if I wrap float around it, it will become a float and display it with a 1.0, okay? The machine precision, I guess it's floats are all doubles and complexes are two doubles. Now there's another question, how big is an integer? What's the biggest integer you can represent? Does anybody know? So actually in Python, in pure integers, somebody had the right answer, it's actually unbounded. If you try to represent an integer larger than the native 64-bit one, which would be something like two to the 64, two to the 63, it will internally, without you having to do anything, actually allocate more bytes to represent a longer integer. And so integers in pure Python are, if you want, arbitrary size. But in NumPy arrays, which is what you'll mostly use for scientific computing, you need to fix the size of the integers you're using. So if you have 64-bit integers, you have 64-bit integers, and they're not gonna get bigger just because you try to assign something bigger, okay? But the core integers in the language are arbitrary. So in Python, this is important. If you divide two integers, you get a float, okay? Three divided by two is really 1.5. In many languages, that would give you one. If you divide integers, you get an integer. In Python, it is a float, so it's 1.5, and which is the same as if you divided two floats. If you want to force integer division, which is really getting the floor of the division, right? The largest integer less than the actual quotient use this double slash. Good. Now there's another basic type. Let's move on. Any questions about numbers? Nobody wants to know how to construct them in ZF set theory or something like that. Okay, nobody cares about my stupid math jokes. Okay, that's fine. So the next basic type is a string. They are immutable sequences of characters, which means once you create them, you can't change them in place. You have to make copies of them to make changes to them. It's okay in practice. Literal strings can be written with single or double quotes. Multi-line strings can be written with triple quotes and raw strings are useful for embedding stuff with backslashes. So let's see how that works. As we already saw, these two hello strings are the same, even though I wrote them with different quotes, because the quote is just introducing that it's a string literal to follow. They both say hello. If I use triple quotes like that, then Python will just include the new line character, which is saying that's a multi-line string inside the string, and so A now has this funny, when I display it that way, backslash n there, which many of you will recognize is computer speak for new line character. And if I print backslash n u, I don't know if you can tell, but this is a new line. There's extra white space there, followed by a u, right? Backslash n meant new line. But there's this thing called a raw string, which is really just a way of inputting a string. So if I put an R before that first quote, then when I put that backslash n, it won't interpret as a new line. It'll just be a string backslash n u, okay? And there it is, backslash new. Why am I sort of laboring this? This is very useful if you are putting mathematical formulae into your figure annotations, labels, and things like that, and you can write them in latex math, like as you can just put a label on a figure in matplotlib, which has integrals and whatever written in pure latex. But in order to do that, there's a lot of backslashes and you need to use these raw strings or else they won't be interpreted correctly, okay? So here is the first bit of a famous float, 3.1415, which I have assigned to the variable a, and there are various ways to convert that into a string in order to print it with different kinds of formatting. So a simple one is just converted to a string, it's draw a, gives me back that, and that plus was concatenation. There is, I don't wanna go in this in too much detail. These examples are here to make you aware of them more than me explaining them in this case. Here, this is called sprintf style string formatting that percent 1.2f means put a float there with two decimals after the point and the percent s means put a string there and then I have this symbol percent there and then a tuple with the first thing that goes in there and the second thing which goes in there. So if I execute that, I get blah 3.14 high. And then there's this new style string formatting which is even more sophisticated and gives you a lot more control about how it formats. I'm not gonna explain it other than give you the example and say go read the documentation if you really wanna use it, okay? Okay. Lists. So these composite objects are basic data types in Python so Python works, they're very efficient actually considering how flexible they are. And lists are ordered collections of arbitrary objects. They're efficient maps from index to values. They're represented by square brackets. You can change their contents after they're created. It takes time of order one to look up an entry at a given index, change an item at a given index, append or remove from the end of the list. So make the list longer or shorter if you're changing at the end. It takes time of order n to find items by value if you don't know where they are so you have to search through the thing. Remove items from near the beginning of the list because then you have to move everything else up and it's actually stored in memory in just a contiguous sequence so it has to shift things. So that takes time of order n. We'll talk about slices more in a minute and lists like all things in Python are zero in text which means that the first item in the list is at position zero and the last item is at position n minus one where n is the length of the list. What does that mean? Takes time order one and takes time order n. I just wrote it there and sort of casually kept talking. I don't know. So this is an important concept the run time of an operation as a function of how big the object is I'm doing the operation to and you generally want things to have as low a power of n, the input size as you can possibly get and that is gonna be more important than almost anything else when you're trying to implement efficient numerics. So here is already the beginning of why you would wanna know these things, why I put it up front here. Looking up an entry to give an index, I just give Python say I want the third entry and it knows exactly where that is in memory so it doesn't depend on how big the list is for it to find the third item. But if I want to remove an item from near the beginning of the list that means that I need to say take out the second item and then move the third through nth items over which means I have to actually go through and or the computer goes through and copies them over one step at a time. And it'll do that very quickly. It's not that Python is doing that in some stupid slow way but it still takes a time which scales with the length of the list. And so it's not something you wanna do lightly. It will make your algorithm slow when you start getting very big lists. Then you might wanna rethink what data structures you're using. That's why I'm talking about it. Okay, so let's play with it. We're gonna create a list. The days of the week are Sunday, Monday, Tuesday, Wednesday, Thursday, Friday. So what's this one gonna do? Sunday. What's this one gonna do? Tuesday, Wednesday, Thursday. So this is called a slice. And a slice in Python is introduced by these colons which appear in the square brackets there. This is a slice from two to five. And in Python, slices are always inclusive at the bottom and exclusive at the top. It's like a half open interval, right? Wait. Okay, it's a half open interval. And so this is including the second, but remember zero index, zero, one, two. So Tuesday, third, fourth, and excluding the fifth. These are important that you can slice quickly. If I put in minus one, if I use a negative index, it just refers from the back. So this is the last element of the list. Every other day, so this is a fancier slice. If I have a slice with two colons, zero colon minus one, colon two. So zero is the first element. Minus one is the last element. Two is a step. So it means that this is gonna be every second element from the beginning to the end. And indeed, we see Sunday, Tuesday, Thursday. There is Sunday, skip a step Tuesday, Thursday. If you want your slice to go from the beginning to the end, you actually don't have to put a number there at all. You can just leave it blank. So colon, colon two is implicitly from beginning to end and steps of two, yeah. Sorry, say it again. What's the reason to use minus, that minus? No, it's not periodic, but minus two is the second to last. Minus three is the third to last. So if you wanna probe things which are towards the end, but not quite at it, minus two, minus three, right? You find becoming familiar with how slices work actually is extremely useful for all kinds of things. And all of these little details like how I can go to the end minus something actually is very useful. But that's it. Minus one is the last element, minus two is the second to last element, and so on. So you can read it as n minus one, which is the last element because it's zero indexed. Okay, so of course, at least a few of you noticed that we left out a day. How would we have figured that out if we were debugging? We could look at what we were doing, but that's too hard. So one thing we might have noticed is that if we probed it in some way, we would get six instead of seven for the length of the list, right? And then we go, I left something out. What is the bug, right? And then I'd go, aha, Saturn has been left out. So I can add Saturday to the end with this append. And now, there he is, Saturn has returned. And we might also wanna dress down on Fridays so we can set Friday to be casual Friday. And now, here's our days of the week, Sunday, Monday, Tuesday, Wednesday, Thursday, casual Friday and Saturday. And indeed, the length is right. Now we can do a lot of things with lists. Let me just show you one useful thing. We can sort the list in place just by calling dot sort. How do you, you have three Saturdays. Okay, let's see. So first, we'll look at this days of the week and we see how has it been sorted and then we'll do that alphabetically. There's a fancy word for that, lexicographically. And sorted lexicographically, right? Also alphabetically. Okay, well, let's see. So let's remember tab completion. So if we go and look at days of the week and hit tab, we would see that there are a bunch of methods on it. And one of those methods has the name remove. So if I had, for example, accidentally, tab complete, append monkey. And I even did it multiple times. And then I went, ac, there's too many monkeys in my week. Right? Then I could say, how do I remove them, days of the week, remove, that looks promising. Let's try that. And then shift tab, remove value. Remove first occurrence of value. Okay, well, let's try that. So I could remove a monkey and I had four monkeys and now I have three monkeys. So that worked. That was not the fastest way maybe to do it since I happened to know the monkeys were at the end. So if I wanted to do it by days of the week, dot tab, what else might I be able to do? Pop, pop index, remove and return item at index, default last. Oh, that sounds promising. Let's pop it. Hey, the monkey popped out. Pop another monkey. Oops. Oh, I popped three monkeys. Days of the week. Ha, they're gone. All right. Pop, does anybody know where the term comes from? Yeah, rice crispy treats are a pretty good source of programming knowledge. I agree with that. But in this case it's actually push and pop. It's the metaphor of using a list as a stack of things that you want to deal with and you push something onto the top of the list, which means the end, and then you pop it back off when you wanna use it. It's the language of a stack. You can use a list as a stack like that and it's actually efficient because adding things or removing them to the end of the list takes time of order one. But if I wanted to pop, that's why the default for pop was the end of the list. If I wanted to pop from the beginning of the list it would actually be slower. Okay. So each item in a list is arbitrary. You can have a list of lists or a list of different types of objects. So here's a list with some strings and numbers and numbers of different types. Here's a list of lists. So here, what will happen when I execute this? What will I get? I hear some people saying the right answer. You guys clearly cheated. I know you hit shift enter. No? No, you guys can interpret Python two now? Oh, that's great. Okay, so we're gonna pull out of the super list, this outer bracket, the second item. Well, this list is the first item. That list is a, sorry, zeroth item, first item, second item. And then that's a list itself. So then we're gonna take from that the first item which is zero one, that's a six. So did we get it right? Ha, six. Great. Okay. Any questions about lists? So now let's talk about dictionaries. A dictionary is an efficient map from keys to values. They're represented by curly brackets. They're mutable. That means you can change, you can put entries in and out of the dictionary and you can change the value of an entry. Yeah? Yeah. Yeah, actually it's a good question. You can take a slice. Depends a little bit what you wanna do exactly. So if I have A is list range 10, I should be able to take, for example, the odd numbers. Oh, by the way, so you guys can see A's range just returned something which gives me numbers up to 10. So this is a list of numbers up to 10. So here's that. So if I was comfortable with doing that, I could just B equals A and work with it B. I could A equals that. And now of course it did what you wanted. Of course I didn't remove it in place, but because the removing in place requires moving things over, this may actually not be that different in terms of efficiency. So let's see. Okay, that's actually great. So one, four, fifths. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Does anybody have a favorite number? Nobody has any favorite numbers? 42. There it is. Okay. Now how about value if value percent two equals one for value in A? Oops. So for value, did I get that right? For value in A, there it is. Now that's a much fancier piece of notation. This is actually a way that Python allows you to filter lists in a way that's kind of legible. This is actually called the list comprehension. So this is, with square brackets, I'm creating a list. It's a list of values for each value in another list. So it runs over all these. If the value mod two, that's a percent two, mod is one, which picks out odd numbers. So I wasn't planning on talking about list comprehensions, but actually, this is probably the best way to do that, what you just said, if you're working with lists. It's, I hope, kind of legible, actually. Almost like English, though there's obviously a particular syntax to how to do that. Does this kind of make sense to people or the questions? Partly, I'm comfortable putting these things up here to show you that if you know Python, you can actually do a lot of sort of complicated things simply rather than needing to write a lot of code. All right. Sure. You could do, well, I don't know, we could square all the odd numbers. We could have more complicated conditions and we could have more complicated sources. Yes, do whatever you want. Good. More questions about that? That's a list. So you can tell because it's square brackets in the output. The list comprehension notation returns lists. There's a lot of, okay, so this will always return a list. There are some other cases where things that are iterable actually return a generator, which doesn't explicitly construct the list, but these things always explicitly construct. That's, again, a little more advanced than I was planning to talk about. But yeah. You can also use this kind of notation to construct a dictionary, a hash table, key value pairs I mean it's slightly different, but with curly braces. Can you what? If you don't wanna have, oh, the sort, yeah, sure. But I mean, you have your own notebook up there. It's easy to find out, right? So B, A.sort, shift tab, or question mark. I can just put a question mark and execute it. And this says, oh, this has a very limited doc string. Okay, maybe you need to go to Google to get the full sort documentation. But what it's telling you down here, where it says key equals none, allows you to provide it with a key function. And I think there's, which then it would compare with the key. I don't think that there's a way to give it a comparison where it does an A, B comparison. It's that you can take the list, give everything a key, which then it'll lexicographically order by key. That usually gives you what you want. But if you want to do a comparison thing, there are other sort routines. You do a little Googling, you'll find it, right? Now, dictionaries. So let's, dictionaries are mutable, but all keys must be immutable, which basically in practice means keys can be strings, numbers, or tuples, but not lists or other dictionaries. And that's because once you put an item into the, like a key value pair into your dictionary, if later in the code you were able to change the content of the key, you wouldn't be able to look it up anymore. And it would screw up the dictionary. So you can only put types into the keys, which are sort of literal things, numbers and strings and so on. But the value can be anything. So the dictionary is unordered, but it takes time essentially of order one, independent of the size of the dictionary, to look up a value from a key, add a key value pair, or remove a key value pair, which is quite different from the dictionary where some of those things took extra time. It takes time of order n to find an entry with a particular value, because you just have to look through all the entries and see if the value matches. You can iterate through the entries efficiently. So you can go through them each in sequence, but that's the only way to find something with a particular value. So for example, a telephone directory is very naturally represented by a dictionary, which maps the names, Emmanuel, Francis, and Sebastian, to their telephone numbers. I suppose those are extensions on some kind of internal exchange, because it's not enough digits otherwise. And we can find Sebastian's telephone number with essentially the same notation, indexing notation as for lists, but now we put the quoted string, which is the key there. There it is, 5578. The keys, the values, the length, tells us how many entries are there. And we can check if Francis is in the telephone book. He is with us in. And we can delete his entry, and he's gone. So basic stuff you can do with dictionaries. Quite useful. Actually, all of the objects, compound objects in Python, are kind of internally represented if they have, I don't know, if you have a complex number. This isn't actually true, but we'll take it as the example. If you have a complex number, it has a real and imaginary part. And the pythonic way that it would implement the real and imaginary part as being themselves objects that are part of this compound thing is actually from a dictionary. So the fields are dot real and dot imagine actually are looking up in a dictionary associated with the compound object. Everything's implemented in terms of dictionaries. Okay, let's keep going. So basic types, tuples. Tuple is just like a list, but with round parentheses and you can't change them after you create them. So one, two, three, high. I use parentheses, but if I try to do this, it will complain. I'm not allowed to change the tuple after I create it. The empty tuple has special notation. So if you just have open, closed parentheses, that's the empty tuple. And if you have a length one tuple, you have to put a comma at the end. Otherwise it could just indicate you're grouping something in a mathematical expression. So these are special notation, right? And you see the difference here with this comma, this returned to tuple, which was of length one whose first entry was high. Here, if I just put parentheses, this is just the parentheses really don't matter. They were grouping the creation of a string, but that didn't matter. And so not length one actually refers to a string. Okay? Okay. And in fact, if I try to pull out the zeroth element of that, I just get the first character of the string. Okay. Finally, I think, or just about finally for today, we'll talk about control flow. So we've talked about the basic data structures in Python. And now we wanna actually think about how to make programs that, you know, do things. And so most of you have programmed, so I shouldn't belabor it, but control flow is just the way in which the order of steps is taken by the interpreter through the program. And control flow is ways in which you can cause the flow of the program to change. So that's conditionals like if, then, else or iterations and so on. One of the important things about control in Python is that the way Python defines a block, which is the set of statements that should be done if you do, say, this if conditional, is by indentation. White space matters, okay? If you indent, you're going into a block and if you go back out, outdenting, you're coming out of the block. In C, that's indicated by curly braces. In, I don't know, in Shell script is curly braces. In Perl, it's curly braces. In Python, there's just indentation, and so the white space matters a lot, okay? And that's, some people hate that. I actually think it's great because it means that you can visually see at a glance the control structure of a program just from where the lines begin, okay? So, not to belabor it, if, L, if, and else, what will this print or what will it do? Will it print, yep it is, not this one either, or not at all? Not at all. So the if, two greater than three is false, so it doesn't go into that block, then it checks else, if, three greater than four, nope, that's also false, so else, the default case, and it has these two print statements which are on the same level of indentation, and so it prints them both, not at all, okay? Four loops iterate through elements in a collection. This can be a list, tuple, dictionary, array, or any other thing which has sort of a, you know, natural sense of having a collection of sub-objects, and that's the pythonic way to think about iterations. You should always think of an iteration as being over a collection of objects, not just with counters going up and down and things like that, that's almost always the right way to think about a for loop. So here, what this will do is, this is four i in range five, so range five gives me the numbers from zero to four, so it's, what will happen is this block, which is indented, will be executed with the variable i taking the value of each consecutive number from zero to four on each run through the block, so it'll be executed five times with i going from zero to four, and we get, we get crash, there it is. The cube of zero is zero, the cube of one is one, the cube of two is eight, the cube of three is 27, the cube of four is 64, okay? We have that days of the week, for day and days of the week, that was a list of strings. Today is, oh, they're still in that funny sorted order, today is casual Friday, no, it's not. It's not casual or Friday today. And four key in the telephone book, key's telephone number is stratell key, which pulls out the value, right? So I can iterate over all the keys in the dictionary with this very simple formula. Emmanuel's telephone number is that, and Sebastian's telephone number is that. Sometimes you do need an index, you need to know, are you on the zero, the first, the second run through the for loop? Notice that in these examples like here, for day and day of the week, there is no variable telling me, there's no symbol telling me which run through the for loop I'm on. I just know what day I'm on, right? Sometimes you do need to know which one you're on, and so the pythonic way to do that is to do a for loop over an enumeration of words, and enumerate words returns for each word a tuple, so it's a list of tuples. The tuple is zero than the first word, one than the second word, et cetera. And you can see here, Python always allows you to do multi-variable assignment. If I have a tuple being returned by something, then I can unpack it into two variables at the same time. So for I word and enumerate words, I can get the number of my iteration and the word. Is that reasonably clear? Some people have totally tuned out, some people are kind of tuned in, some people given up entirely. Okay. And, sorry, say that again? Yeah, so if I pass to print just a variable which is not a string, it will try to, by default, it'll try to cast it to a string by doing straw of that variable and then print it. So you don't have to manually cast most things to string, turn them into strings before you pass them to print. You only need to do it if you want to control the formatting in more detail. In this case, it's not important. So a while loop repeats a block of code while a condition holds true. So this one, what'll it do? Initializes, this is a little bit more like what you'd see in sort of other programming languages where you just have counters that are changing. So here, I start with a variable set to five while it's positive, I bark, and then I decrement the variable by one. So what this should do is countdown from five to one. There it is. Okay. So functions are kind of a form of flow control. They're also kind of object. Any code that you call multiple times with different values, you should wrap up in a function. So here's an example, the classic example. Return the square of X. Now let's look at this function definition. This is actually something very Pythonic. So def, that's introducing we're defining a function. The name of the function is square. It's argument, it has one argument. We're gonna call it X, okay? Then I indent, this is a block. So the function is here and there's two lines. What is the first line? The first line is a triple quoted string. It's just a string. It's not even a command, it's just a string, okay? In Python, if the first command or line in the definition of a function is a string, it becomes part of the documentation of the object. So because I put that string there, if I execute square question mark or I hit square shift tab, then we see down here that Python could pop up this information for me, signature square of X, doc string, that's what this is called, return the square of X, right? And then the file is in some I Python input thing and the type is a function. So this string is the documentation string for the function. So it is considered highly Pythonic good practice to put a documentation string as the first line of any function you write which describes in human language what your function is doing. Because then when you've forgotten all about your code sometime later or you give it to your friend or whatever, it's already commented and all the help is there for yourself, right? That is part of writing good code when you're doing complicated things. So once you start wrapping things up in functions, you should always put these commands. Now, of course, this function is fairly simple. It does is return X times X. So if I square nine, I'll get 81. Here's a slightly more complicated function. I print and square X. Again, I've now got a doc string here so I can look up the help, right? And this printed the 64 which appeared here not as output and then it returned the 64 as output which is why it was printed a second time. Functions are objects just like anything else. They have a type. So if I have square, the type of square is a function. I can assign it to another variable. So now A contains a reference to the function object that square pointed to. And I can call it A of five is 25, right? So this means that I can pass around functions within my code. It's actually quite useful for a lot of purposes. Callbacks in object oriented things, hooks. Sometimes in numerical code, you can pass in a function to another function which tells it how to evaluate the gradient and then that function will implement a gradient descent for you, right? So that's why it's useful to be able to pass functions around. So here I guess is our final example of the day. I thought I included one more example but I guess I moved it to tomorrow which is fine. We'll stop a little early. So what will this do? And I apologize, I didn't put doc strings so you can't cheat in answering this question. What will this do? Some people look intensely like they're doing something else. Some people look mystified. Some people clearly know what's going on. So okay, so what it's gonna do, what we can see and then we'll talk through it, is it prints and call it and in test, right? So how did that work? Well, I called it test. I didn't call test, I called it test. So I passed to the call it function a function, test is a function. So then this executed here and call it then it called the function that was passed to it which happened to be test. So that went here and it printed in test then that returned that came here and then that returned. I, good question. I think if you just outdent it's enough but I always put a return def blah print hi. You see my approach to Python, right? Oh, does it work later? Oh yeah, good, right? Okay, so any questions about stuff we talked about today? Yeah? If you're using an array and you wanna do like remove operations and things like that. So, non-py arrays we'll talk about in a lot more detail tomorrow. I left them for tomorrow. But they are not naturally resizable. There are tricky ways to do it. But you don't, they would be no more efficient. If you wanted to pull something out it would, it's also stored continuously in memory. So if you wanna pull something out of the middle you still have to move everything over, right? So it'll be identical. So for many purposes arrays and lists are similar. Arrays are faster because the way they're stored they know that every entry is the same size in memory and so they don't have to, they can just jump to things much more efficiently. That's the advantage of the numerical arrays. What's that? Can you square an array? Yes, you can square an array. I can square arrays in all kinds of ways. We will talk about it tomorrow, but okay, why not? So let's say that we had A was an array. One, two, how about that one? Do, do, do, do, do, do, do, do, do, do, do. So I can, well if I do A times A that is element Y squaring, see? And if I do A at A, can you raise it to a power two? Sorry, I didn't understand. Oh, here, this square, here, yeah. But what it'll do is exactly what I just did. So if I did square A, is that what you mean? So that, because the code used a star inside, does the first version, which is element-wise multiplication. I don't know why it's so slow returning results to the notebook, but it'll be this 1441 case eventually. So that's actually, it is a good point. So in Python, it is a very, objects have types, but it's not a strongly typed system in the sense that any code, unless you explicitly put a check, is this of a certain type that you want? Everything will just try to run and use whatever meaning it can for the object, for whatever functions you call it. So in this case, I wrote the code thinking of X as just a number type, and I wrote X star X. For the array type, A star A does element-wise multiplication, and I have no idea why this is not returning, but the code should just work. But it'll work with that meaning, which may or may not be the meaning you had in mind. It's not the matrix square, right? It's the element-wise square. So it's something that it's, I think it's called duck typing, which is that if an object walks like a duck and quacks like a duck, it's a duck. We don't check it's a duck. We just, if the object can quack, then we'll call quack on it and move on, right? And that's true here, too. Well, that's what this is a representative of us. There's no type checking. Which occasionally leads to bugs. I mean, type checking is useful in a lot of code, but usually it just allows you to develop things faster, and then you have to be a little bit careful when things get complicated, not to pass the wrong things, or to explicit type checking yourself. Okay, I don't know why this thing stopped. Right. So I think, yeah, I think that's it. So I could, I don't think, I think we should just call it a day. It's been a long one. And tomorrow, hopefully, even those of you who are Pythonistas will actually get something out of what we do, which is going to be going through NumPy matrices a bit, slicing more sophisticated slicing tricks, sparse matrices and compressed representations, and how to use those tools in Python to actually construct spin chain and Hamiltonians and do a diagonalization study using both dense and sparse diagonalization, which is all actually very easy once you know the tools. So that's the plan for tomorrow, but I don't think we should start now because there's not enough time to actually get to the fun part and let you guys start playing with it. So, all right. Thank you.