 As much as I got paid, I will pay. So, apparently the self-organization for tomorrow worked very well. And the schedule is that there is going to be another lecture by Fernand Kraut from 3 to 4.30. Then the coffee break will be shifted from 4.30 to 5. And then 5 to 6 markets in the rich is going to give another extra hour lecture. Ok. This is self-organized, as I said. So, it's not... You can follow it or not. Ok, but for now, let me leave Chris Slavman to his next two hours of lectures. And the server is up. Now is it on? Yes. Good. So, you guys are gluttons for punishment, organizing extra lectures. And you came back for this second Python extravaganza. So, ok, I didn't know there were so many masochists in physics. So, I wanted to... There were various issues with the rather flaky server. So, there are two ways for you to follow today's lecture. If you have, as some of you do, Jupiter already set up on your computer locally, then the notebook is available for download. If you go to the ICTP website for the school and go to the program link and then scroll down to... Not that one, Thursday. This set of slides for this 4 o'clock section. You click there. And then you go to the actual IPython file. So, if you want to run it locally rather than dealing with the server, please go ahead and download it. If you already know how to use Jupiter because you have it locally, you can probably figure out how to do that and just run it on your own machine. Alternatively, you can do like we did yesterday and go to HTTPS colon slash slash Jupiter dot ICTP dot IT. Log in. And hopefully it'll log in. And then go to examples and click on use for intro to 2018 ICTP dot IPy and the. Everybody there? Everybody who plans to do it? Anybody who's actually going to follow along at home, are you still waiting to get to the examples page to load it? Okay. I'll assume that's good. I've already got it open here. So, today's lecture is continuing where we left off yesterday. But now we're going to actually dive into the numerical array functionality in NumPy and some sparse array functionality in SciPy. And by the end of the lecture, so we're going to start with basic things using those libraries, using arrays, slicing, et cetera. By the end of the lecture, we'll do some diagonalization of transverse fieldizing models because that's what we're all here to do, clearly, in this notebook. So, the first thing you have to do to use the numerical libraries, scientific libraries and plotting tools particularly is execute this command, %pylab notebook. Oh. I'm running this locally. I forgot I turned on a thing which tells me how long stuff takes to execute. So, on my machine, that took 540 milliseconds. It won't say that on your copies of Jupyter. But if you want to learn how to do that, come ask me after class. So, anyway, this is how you... Okay, so let's break this down actually. It seems like a simple command, but it's two things. The % is actually a command we are telling Jupyter, not a command we're telling the Python kernel. It's called a magic, a Jupyter magic, or ipython magic command. And what this does is actually sets up the Jupyter interface to be able to deal with interactive plotting. That's the main reason you have to tell Jupyter you're loading these libraries. What it also does is imports a large number of the basic numerical functions directly into the namespace that we can type array and get the array function. That's considered bad practice in bigger programming projects, but for interactive stuff it just saves a lot of typing. So, that's what we're going to do. And then this notebook here is actually telling the plotting tools that we want to use a notebook to do the back end for how we plot. And there's a couple of options for how to do that. If you're running it locally, sometimes those are ones that you can use, and when we're running through the browser like this, this is the best way to do it. So, those are what magic commands are. This command here is actually sets up roughly the same stuff, except for it doesn't load all of the sine, cosine, array, math functions, et cetera, directly into your namespace that you can type. And so, this is sort of considered better practice by people who like forcing everybody else to type more. There's some reasons for that. OK, so, that's it. So, what is the main workhorse in numerical computing with Python? Scientific computing with Python is a type, or called a numpy array. So, arrays in numpy are multidimensional. They can be vectors. They can be rectangular. They can be three tensors, four tensors, five tensors. You can make as big a tensor with as many indices as you want out of it. So long as it's each index runs over some finite number of values. So, they're multidimensional arrays. Every entry in the array has to be the same type. It has to be an integer, or it has to be a floating point. So, it makes them different from the lists that we learned about yesterday. The type of an array is what's called a d-type, and that is a more refined typing system. That means that you say, I want an integer, we'll get into this in more detail in a little bit, of size four bytes rather than just an arbitrary integer. You have to specify that when you're working numerically because the system needs to know how many bytes to allocate to do the computations. And the arrays, they're efficient maps from indices to values. They have minimal memory overhead. Arrays are mutable. Their contents can be changed after they are created, but their size and the type of object they contain, you can't easily change, so you have to create a new array if you want to do something like that. So, what are arrays good for? Sort of at a high level. Representing matrices in vectors, so doing linear algebra, storing grids of numbers, so plotting numerical analysis, storing data series, so data analysis. I just put here getting changing slices because it's such a useful actual task, although that's not at a high level, that's pulling out regular subarrays. Arrays are not good for things where you want to change during the course of the run of a program the size of the array. Probably you should use a different data structure. Heterogeneous objects or non-rectangular data. So, if your data would be a bunch of zeros somewhere, maybe the array is not the best choice. Okay, so, the practical part, I'm going to get rid of the menu bar on mine so we can see a little bit better. Okay, so, this creates an array. We actually already did this yesterday, so this creates an array, which is a vector, length three, which we can discover by using, yeah? Right, so, good. So, if we had not used percent pi lab notebook and we'd used this other way of loading num pi, then we could refer to the array constructor function by explicitly writing np.array, and we can still do that. There's an array. But, because we used the pi lab up there, this imported array into the global namespace so we don't have to specify num pi dot or np dot. Okay? And what I was saying is that that's considered, you know, bad practice because you actually, this percent pi lab command is importing a lot of functions, sine, cosine, array, random, a bunch of basic sort of numerical functions into the global namespace and so people who are purists want you to always type np. and then whatever function is in that library. And there's some merit to that argument when you're doing something complicated, but when you're just sort of doing quick work, it's often easier not to have to type it all the time. So, does that answer the question? Yeah. So, np.array and array, now that we've imported them, are references to the exact same function. So, it's just two different ways to call the exact same thing. Okay? So, arrays have various properties. So, the shape in this case is a tuple. The shape is always a tuple. It's a length one tuple whose zeroth entry is three, which is just telling you that it's a length three vector. It has a d type. That was the data type that I mentioned earlier. 64, which means it's a 64 bit float, floating point number. It has a size. That is just the number of entries in the array. So, if it was a multidimensional array, it would be the product of the values in the shape. A three by three array would have a size of nine. We access the elements using square brackets just like for a list. So, there's one of a root two from the zeroth element. We can change entries in an array in place, no problem. We just assign to them. So, we just squared the zeroth element of the array and now we see that it is squared of two. So far, so good. We can create a 2D array by passing the array function a list of lists. We met those yesterday, but they have to be all the same size and shape. So, here this is going to be a three by three array, which looks just like what I wrote, but it's stored internally now in this array data structure. One zero zero zero zero one zero one zero. Yeah. So, all of the numbers in Python and NumPy and SciPy essentially are stored internally just in a floating point representation, which means as, well, in binary, but as a number with a decimal expansion up to some finite length. There's no exact representation. So, there are some libraries for Python. One is called Sympy, which allow you to do more symbolic manipulation and would be able to represent an object like radical two. I don't use them. Some people do. I think as soon as you want to do things which involve exact representations like that, you, Mathematica and Magma are probably better tools. But some people like Sympy. Is there anybody here who uses Sympy or Octave? Yeah. Did you hear that? So, he went, it's not great. Right? And I think that's basically accurate. So, numerical things, Python's great. Data analysis, anything where you're not worried about getting exactly square root of two. But you know, there is an entire tradition of sneaking around this by if you happen to know that your exact representations of square roots will show up, you can often take a floating point and find the closest A plus B root 5 to that floating point number and get A and B, back them back out. There's tricks to do that efficiently, actually. So, then you can do the computation quickly floating point and figure out what your exact answer would have been by cheating. So, okay. And you should cheat. So, here is the shape of B. It's three by three. The length to tuple, three, three. Here is the data type. Well, here I passed in a bunch of integers and so I didn't explicitly tell the array constructor what data type I wanted it to use. It looked through the list, saw that they were all integers and decided, oh, you want to use an integer. And so it gave me the default integer data type, which is in 64. That means a 64-bit integer or an 8-byte integer. The size, nine, in the top left corner is a one. Okay? So, now, here is an exercise. Change the last row of B to have a two instead of a one in the middle position. Go. Has somebody done it? How do I do it? B, two, one. Two, one equals two. All right? And let's see if I'm right. Hey, that looks pretty good. Okay, very good. Now, here is a warning. There is a type called matrix instead of array in NumPy. This is especially for two index like square or rectangular arrays, but it is not to be used anymore. Okay? It's being removed from NumPy at some point in the not distant future because essentially all it does is produce bugs because it looks almost like an array, but the semantics are slightly different and if you think you're getting an array and you call matrix multiply and then it does things transposed or something and then your code explodes and your computer turns into a puddle on the ground. So, just don't use it. Don't confuse yourself. If you see a matrix, if you see an array on it, don't use matrix type. Okay? Good. Basic linear algebra. So, what's the most basic thing you can do with vectors and matrices? Well, you can multiply them or if you're very fancy and you're really into matrix product operators and tensor, yeah. Oh, oh, oh, yes. I think what you're saying is that if I did this, if I said, let me call it a lal, is one, two, three, four, like that and I looked at my lal. It would be a list of lists. So, the lal, the lal, I accidentally hit the key in the numbering. The lal is not an array. It's actually a list which has lists in the items. So, if I want to get the bottom right corner, if I wanted to think of it in this array, I have to pull out that list and then I can, from that list, pull out the bottom right corner. But the array data type is actually to be thought of as a full rectangular thing and then it allows indexing where you just put comma to tell you where you want to look. Whereas here the list the list doesn't know that necessarily the item in entry one is actually another list which it could then index into. Does that make sense? So, the array is different than a list of lists even though we construct them, the simplest way to construct them is by passing a list of lists to the array. Yeah? Okay. Excellent. Good. Any questions? So, say we wanted to convert an array of integers into an array of doubles. We can do that in a couple ways. So, first of all we're going to have to create a new array. We don't do it in place. So, if I have that array of integers let's see, was that B? There's an array of integers. Right? So, one thing I could do is I could do C is just one dot times B because what I just did was use a floating point number multiplication the integers became floating point and then it created the right kind of array. So, that's a short way to do it. Another more explicit way to do it would be to say C is an array B and I explicitly tell you the D type. So, if I wanted it to be float I could say float 64 and now that's the same thing. Okay. And that's what we need to do if you wanted to use a non-standard number of bytes per entry in the array. Like, if you didn't float 64 as the default that's a double float. If you wanted to use 32 or something you'd have to be explicit. Okay? Any other questions about constructing? We'll get to other constructors and things like that in a bit. So, and D types in a bit more detail as well. So, there are two kinds of basic multiplication element wise multiplication and dot products or contraction of indices. Right? If you want to do MPO or tensor network stuff you might even have things which have three or four indices and you need to do all kinds of contractions on it. And all of those are possible but the basic kinds are going to be just multiplication. So, if we have two vectors which we've just created so the first one is 1, 1 over root 2 the second one is 1 minus 1 over root 2 which for those of you who want to have the geometric representation of the vectors looks like this. Right? So, the dot product is what? Yes. Very good. If you multiply my arms together you get nothing at all. So, what does this do? A times A? This is element wise multiplication. So, it's going to give me a vector which all it did was multiply the first element of A by the first element of A the second element of A by the second element of A. Okay? The special notation A at A here, I'll make it very clear at like that. That is the multiplication operator for dot product. And to within floating point rounding errors you see that 1 over root 2 1 over root 2 is a normalized vector because it's dot product with itself is about 1. Okay? There's also we can compare the norm of A which is just a function which computes the norm of the vector in the standard dot product and if you look at this these don't agree so why is that? Yeah, the next box kind of gives it away the norm of course is the square root of the dot product and now I hope it does it actually agrees right? So those are all 1 for practical purposes so now what does A star B do? Element wise multiplication a half minus a half and A at B 0 right? Orthogonal vectors dot product 0 there are many many more functions for doing linear algebra operations numerically we'll use some of them as we go so I'm not going to try to just front load at all so the first use you can think of numpy arrays as things to do linear algebra matrices and vectors kinds of stuff second thing primary use of numpy arrays is to hold grids of numbers which I don't necessarily think of as being a vector they are for example the values of a function on a grid of points and that's how I can then plot a function right? so here we're going to plot a sine wave and maybe I'll just execute it and then we'll go back and figure out what this code does it's a sine wave so we are using the notebook mode that notebook back end this isn't actually maybe before I explain the code I'll just show you that with those few lines of code we got this nice plot it was labeled and in fact it's even interactive I can click here and I can move this around inside my notebook I can zoom I thought I could zoom oh there it went it just took a second right and so I can interactively manipulate a little bit when you are done with the notebook mode on a figure you can click this button and it will disconnect the interaction between the kernel and the front end and then you won't be able to change it anymore but it just it gets rid of some overhead that the kernel is dealing with it doesn't really matter if you only have a couple figures in your notebook but if you have lots of them you'll end up wanting to do that okay so let's go back here what is this doing so this first line is creating a equally spaced array of a hundred numbers from minus 2 pi to 2 pi and assigning it to the variable x so we can take a look at x and that is just an array one dimensional array with a hundred entries in it one two three five six seven eight nine ten eleven this might take a while okay it has a hundred entries look it's clearly divisible by five because it's printed so nicely so it must be a hundred so then that's minus 2 pi that's 2 pi and these are equally spaced in between okay so Lin space created that nice regular grid for us then this line here y equals 0.5 times sine x so what did that do? so this sine called on an array it's not even if it was a two-dimensional array the matrix function valued sine okay we are not doing the sine map on matrices we are doing the element wise sine okay and it's actually interesting it's probably only sort of physicists above a certain pay grade who would even think that might be what it meant okay but that is not what it means and don't get confused there are ways to do exponentials of matrices but the default things won't do that so here is the sine element wise of x so this sine of x is an array of length of 100 with each value of the sine in here and then that multiplication is element wise and that's why the star by default is element wise multiplication because this is actually the most common you would use it doing the dot product is more specialized than just writing functions that you're gonna plot right so y is 0.5 times sine x creates an array of 100 things which are the values of that and then this code here is our first use of map plot lib so figure creates a new figure plot x, y with two vectors of the same length or arrays of the same length plots all the points this grid command just turned on grid in the background there it is and then the x label and y label gave us the x and y labels unsurprising let me remind you this R and then a quote was introducing a raw string and the reason I did that and then I put dollars here is because map plot lib knows to interpret this as a latech math expression then and you will see over here 0.5 sine x because there's a backslash sine there that sine is actually in the font that's appropriate for a function name in math mode the x is italicized as a variable should be just like latech would do yeah horrible font sorry, city yeah, they look bad in your notebook yes yeah okay, so there's actually I probably because I'm running it locally is why it looks different there is a configuration option for map plot lib which I just have on which tells it to always use tech rendering yeah, let's see if I remember how to do that so npl.rc rcparams text.useTek I think oops wrong brackets rcparams true so okay, this is arcana but of course how would you figure out how to do this if this is what you wanted to do and you couldn't remember my arcana is you would Google how do I turn on latech in you know, map plot lib and you would find something that explained it very nicely for you okay so rcparam npl is the map plot lib library it's how I imported it up above rcparams is basically how you configure it all these kind of global options and text.useTek is the global option we want to set I have it set in a configuration file I won't tell you in this lecture how to do that somewhere so that every time it loads it automatically turns this on but if I wanted to turn it on or off I could set that equal to true or false and let's see if it makes this uglier yeah, see, it's uglier do you guys see ugly right, ugly not beautiful latech and then we can go back here and go and turn it back on true and beautiful right it's so easy just one shift enter and suddenly from ugly to beautiful and back again come on yeah no, so you mean that it looks a lot like matlab plotting does yes so percentPilab is what implorated those commands that look like matlab plotting so if you guys know matlab I haven't referred to it because it's like the death star in my view but anyway if you guys know matlab then you'll see that the plotting functionality like the plotting commands look very similar and that's actually what percentPilab imports it imports a bunch of plotting commands that look like the matlab ones there's another way to interact for the plotting library when you're doing more sophisticated things if you get into it which looks a little less like this but this is actually you know it's intentional it's so that people can convert from using matlab more easily any questions about that? I stopped giving specification oh, ah, yes okay, so now we're getting into the nitty gritty of how does this know what figure I'm acting on so in Pilab which is the matlab localite plotting system it's stateful it knows there's a global variable which contains what is the current figure and what are the current axes and if you don't you can in all these commands there's a way to actually specify those explicitly if you're doing it more explicitly but if you have these plot grid X label Y label commands without specifying it they just act on whatever the current global figure is so in fact let's see if this works right now this figure is live I can even go down here to another cell if I'm what is going on okay, that got my focus there and then no, I'm not allowed to my focus back okay, fine so I could for example change the X label to the square root of X squared and if I go back up here it's still live on this plot because the current figure is still this one because I haven't created the current figure yet if you want to work with multiple figures with multiple subpanels then you'll start actually referring to them you can get the current figure get the current axes assign it to a variable and then say I want the grid to be turned on on that variable or on that axes but for simple plots you don't need to do that and that's why it contains this little bit of global state so you don't have to keep track of it once I disconnect this I can execute this command but it won't live in the notebook anymore it would in some other back ends but in the notebook once it's disconnected it's disconnected okay ah, okay so I we can do this now I think it should have probably been later I think I put it out of place so let's just start thinking a little bit about performance these aren't particularly good examples but it does give you an idea of how to time things so remember range 1000 indicates a range from 0 to 999 and this comprehension which we actually saw yesterday is going to create what? it will create I squared for any I in L so it'll create a list of 1000 squares okay and if we do that with the list and a list comprehension this is the pure python we can use this percent time it magic command to see how long this little snippet of code takes to execute and what percent time it does is very useful it actually runs it and depending on how long it takes to run it decides how many runs to do and then averages over the timing so you get a better estimate of how long it took in case there were weird fluctuations from your computer deciding that it needed to you know download something in the background or whatever so here you see that this took 360 microseconds plus or minus a microsecond per execution of that command if we do it with an array so a range creates an array from 0 to 999 but it's an array type instead of a list type let's see how long it takes actually don't know 1.2 microseconds plus or minus 5.6 nanoseconds per squaring every number in that array okay so this is one of the reasons that you use arrays if you're doing numerical work is that this for an array this is calculating the exact same quantities but it is doing it highly optimized internally okay a squared whereas if I do it in sort of the pure pythonic list way it's this is 300 times slower okay that adds up so that's why that's one of the reasons we use NumPy arrays to do numerical work ah a percent sign before the command right so this is so these are called magic commands because they're so magical but what they are are commands to the Jupiter system rather than commands to your python kernel in the background and they do like this case what it does actually this is some metacode which basically sits there and tells the python kernel in the background to run this look at this a thousand times seven runs of a thousand times each in order to estimate do this timing for us and in this example there's no particular reason it had to be a magic command but it's very convenient and then it has a nice output which is sort of built into the notebook system so a bunch of profiling commands are magic commands like that okay and here just so we can see what we are calculating there is a versus a squared most of you will recognize that as the transpose of the square root function half audience fine it's half a parabola you guys can keep the other half alright so let's look at some common arrays so a range was the thing that created an array just like the range function created a list of numbers from say zero to a thousand a range creates arrays of numbers but the three arguments to a range if you give them three are actually just like a slice so the first one is where to start the second one is where to finish and the last one is the step size so what will this give us two four six eight who do we appreciate and this next line this is actually gonna do the exact same thing this is a because you often need regularly spaced arrays of numbers in slices there is actually a compact notation for it which is R underscore you can remember it creates a row vector with the contents of the slice and you use square brackets for that so it's kind of like there's a yeah yeah that's because sometimes you want to specify the step and sometimes you want to specify the number of points so if you look right there there's linspace and five so let's take a look they do have different meaning linspace and a range do very similar things start, stop, step and this behaves like a slice so the top is not inclusive linspace starts, stops, number of points and actually this is inclusive of the top and there's two versions because this is usually more useful if you want to think I want to look at a grid of numbers from here to there regularly spaced you don't want to think about well I need it to be I want 100 numbers from here to there so I can make a nice smooth looking plot and I don't want to think okay what is 99 minus 37 divided by 100 to figure out what the step size is and then if you did that you'd get it wrong because actually it should have been divided by is it 101 or 99 I don't know there's some number of bins that you could divide this thing into and it's not the same as the number of points they differ by one by the time you've thought about all that the guy who knew to use linspace is already done plotting the thing so that's why there's two versions of this function to create regularly spaced arrays of numbers and actually they're both so useful that there's a compact notation for both so you can use r underscore square brackets and then a slice 2, 10 in steps of 2 and that does the exact same thing as that a-range did there this allows you to very compactly say I want a row vector that has this step size and this is really you know tricky if I pass as a step size an imaginary number then this r underscore thing will do the linspace okay now you might think like these are really the technical little but these are really useful actually because you often want to create regular arrays so knowing these compact things actually is just kind of convenient which is why I put it in okay now what are some other ways to arrays that are common you might want an array of ones that is a 2 by 2 array of ones you might want an array of zeros there is a 3 by 1 array of zeros and that is a column vector if you want right what do you think this one does does anybody think that it is going to look at you creepily because that is what I hope it will do no? no takers? the identity matrix ba-ba okay so i3 gives me the 3 by 3 identity matrix and then what about diagonal diagonal matrix with 1, 2 and 3 on the diagonal okay and I'm giving you these they all have more arguments they all have more documentation they all have variations to create other versions like maybe you want a matrix which is basically diagonal but it's a little bit off diagonal it's like the not main primary what's it called main diagonal is it the main diagonal? the sub anyway say you wanted it to be on the sub main diagonals then you can do it but you can read the documentation to figure out how okay okay now here is a very useful one because we actually produce so much noise in physics we need to generate random numbers all the time right np random.rand gives me a 2 by 2 matrix of uniformly distributed numbers between 0 and 1 there's all kinds of other random number generators so this one np random.exponential here I've given a few more options scale equals 3 size equals 3,3 what do you guys think this will do? well it's clearly going to generate a random matrix the size is just that it's 3 by 3 so it's a 3 by 3 array of random numbers and looking at those numbers can you guys actually do some machine learning and infer what distribution it came from? he's good very good this is an these are sampled from an exponential distribution whose mean is 3 which kind of makes sense they're kind of on the scale of 3 which is where you expect the bulk of that distribution to be but with only 9 numbers sampled from the distribution you'd probably have a hard time reconstructing the histogram of course it's easy to get a lot more of them just by changing that parameter let's get a few more there's a lot of them ah cool I wonder do you think we can get a histogram? hist oh I didn't save them anywhere so let's put them in something will this work? so what do you... oh that's interesting what did it do? I had 3 by 300 so this just made a histogram for me of each of the rows of 300 random numbers and it said the first row was blue the second row was orange and the third row was green so you can actually kind of see the fluctuation scale between independent 300 samplings of this exponential just from that okay cool good let's keep going so in array d types I mentioned this already the data types for arrays are more specific than the basic number types or numeric types for python because you need to actually you usually don't need to think about this unless you're doing very serious numerics but you do in principle need to specify how many bytes or bits each number in your array is going to use so integers they have various names you can call it n16 for 16 bits or i2 for integer 2 bytes unsigned ins floats bools so unsigned ins are non-negative fixed length strings an array can hold a set of strings it's a rather unusual thing to want to do but you can do it but they have to have fixed length so all strings of length 3 that would be okay so if we look at the d types we already saw this basically by default if I pass in all integers in my array constructor I'll get an integer d type out the 64 bit integer on my computer is a default if I pass in something with a float it'll end up being a floating point array float 32 this is a reference to the d type if I want to specify it or d type i4 that's a shorthand notation when you're using constructors to just say i and then the number of bytes so this is all to indicate how you control this okay so let's keep playing what is this block of code gonna do oh you know this would have this works strangely now that we're doing doing it in the notebook interface last time I gave these lectures I actually had in a more interactive interface so this is gonna sort of not pop things up right away but let's see so the x creates a regular grid and pi to pi of 100 points y1 is the sign of x y2 is e to the x divided by pi so we can execute that then this thing is gonna create a figure and plot x and y1 and x and y2 and labels those lines but notice right now we don't see any labels we just see the lines and the axes the figure was created now without disconnecting it so I'm not gonna hit this blue button up here I'm gonna scroll down I'm gonna execute x label and y label and you see here I might be able to zoom okay we can see a little bit more anyway x and y appeared then I can go to the next one legend it created a legend and that's where it put those labels okay and notice they're beautiful pure golden latek okay so and if we wanted oh it already put the legend in the upper left it was pretty smart about that but if we wanted we could put it in the lower right and now we go back and look it's moved and put it down there okay okay so this GCF we don't need to do it because we're using the notebook back end this gets the current figure and will basically make another copy of it in my notebook but it already created it here so I didn't really need it but that's what GCF does sorry what? you can put it wherever you want the easiest places to put it are the ones like lower left, upper right and more precisely where you want it you just have to read the documentation about how to do it and how do we find documentation while we're doing things interactively shift tab okay good GCF killed your figure? well that was mean oh it didn't put another one in there well that's sad the interaction between the back end on this server and the notebooks is a little bit flaky I think then it's because of how you haven't set up I won't take any responsibility but I'll help you fix it after class if you really want to okay what about some 2D visualization so what do you guys think this will do? we already saw this command that creates a 30 by 30 array of random numbers uniformly distributed between 0 and 1 and it assigns it to a variable we called IMG for image then we create a figure and then we do imshow for image show and image and then I set a color map which basically tells it what color scheme to map these numbers between 0 and 1 into colors with that's called a color map and then I turn on a color bar which tells us how to read those colors so there it is a random map of hotness and with a color bar over here indicating what dark to light means 2D visualization in a couple lines good and then so this command isn't strictly necessary here but I want to tell you what it's doing is actually closing all the figures that you've been creating in the system out of memory so if you create lots of figures you normally don't need to do it but if you create lots of figures like 20 or 30 it will start complaining and it might slow things down so this is just a good way to close them all they're still in my notebook that didn't remove them it just removed the interactive part of the figure from memory so it wasn't tying up the kernel anymore so that's a useful command okay now slicing we've already seen slicing a little bit so let's create an array a range 0 to 10 so it goes 0 to 9 and we know what a slice is it goes from start to stop so this is a basic one it went from the third to the second to last right from 3 third to minus 2 not including the minus 2 3 to 7 this next one takes every other entry from 3rd to 8th the empty slice actually just returns what it gave it goes implicitly from start to end but then of course we can actually use a negative step and so that empty slice just goes backwards now so that was a quick way to reverse the array okay now an important thing about slices of arrays these things are views into the data they're not copies so if you slice and then assign to the slice you will change those sub pieces of the array that's part of what makes them so useful just do it in place in memory okay so we'll start playing with that a little bit more let's do a 2D example so here what did I do I have an array A range 20 so I created the numbers from 0 to 19 as a 1D array then I reshaped it into a 4 by 5 array which was okay because 4 times 5 is 20 so that's okay I didn't change the total number and that created this nice 2D array that's 4 rows by 5 columns of the numbers from 0 to 19 so let's just work through these slices well this should give me the last 2 columns did it then here we're just using the fact that if you don't put a number in it'll default to the beginning or end depending on what position it is so from 3 to 5 a 3 colon all of these do the same thing but now it can get kind of more interesting we can take every other row by using a colon, colon, 2 slice in the row position and only the first 3 columns there it is 0, 1, 2, 10, 11, 12 let's go back and look that is the first 3 columns of the first row 3rd roj. Zero in 2nd roj in zero indexed language. Here, we can get the whole matrix and just reverse the rows. Well, here this minus one is in the step position. I have two colons. Then it wouldn't put anything out. So, yeah, it just sort of decides that if you're doing a reversed, then the start and stop essentially are reversed as well. So this is implicitly from the end to the beginning. Otherwise, it wouldn't make a lot of sense to have this notation. Yeah, that's right, though. It does. And actually, if I have this get everything for the column index, I don't even need to write it. I can just reverse the rows, right? But like I said, I can, excuse me, slices are views into the data. So I can change an entire slice or subgrid or whatever I can construct by this kind of notation. I can change in place. So if I look at this command, what I'm doing is I'm taking every other column, sorry, every other row, first three columns. So it's the same slice I had up here. And I'm going to multiply them in place. Star equals means multiply by 20. And there it is. I've now, a, now contains an array where the first three entries of the first row and the first three entries of the third row have been scaled up by 20. And this command here, well, I'm taking the first row, zero indexed, all columns and setting it equal to zero. And there it is. I zeroed out that row. These are slicing tricks. And also very useful to get to know how to slice, because if you can do things with a slice notation, write a compact line that just sort of does the right stuff in the right places, your code in Python will one be compact and two be pretty much as fast as anything you could do in a compiled language, because it will optimize the way it implements that internally. Any questions about that? OK, so there's actually a little bit more fancy indexing you can do, but if it's not, this is even fancier than slicing. If you do this to pull out subarrays, which aren't as regular as you can specify by slicing, then it does create a copy, so you can't actually assign to things like this. So here I'm going to create an array, which is three by three of the numbers from zero to nine or zero to eight. What this is doing, because I've got now a tuple inside the square brackets, I'm actually handing it a, give me the zero, one, and then the one, two entry. And so those are the zero, one, and one, two entries, one and five. And the other thing I can do, which actually is often more useful than this version of fancy indexing, is I can create a mask. So a mask would be an array, which is the same shape as another array, but it's just an array of trus and false, right? And then I can use the mask to only do operations on things where it's true. That's the idea of using a mask. So here, oops, this mask is a three by three array, which is true when a percent two equals equals zero. Sorry, I didn't explain this notation yesterday. Percent means divide this by that and give me the integer remainder. So a mod two. And so this is basically equals equals, is testing whether that's equal to zero. It returns true or false. And so each entry in this array, this is a three by three array, is true or false, depending on if the corresponding number in a is even or odd. True, false, true, false, true, false, true. D-type is bool. And we can take a look, actually, at that mask with this kind of neat plotting function called spy, which is for spying on a matrix. It just tells you where it's non-zero, basically. And so we can see, well, true, false, true, false, true, false, et cetera. So we spied on the mask. That's just a picture. And now if I use the mask as the thing I'm going to index with my array, I get out the even integers, the places where it was true. Now this, I wrote here, flattens, means that it gives me just the set of them. They're not in a rectangular form anymore, in a general mask. Here they were in like a checkerboard. So this is the array for the array to represent the checkerboard shape. And so it just returns them in order, sort of reading left to right, top to bottom in your mask. So this is called flattening the array. And this is also not, like I said, this is not a view into the array. You can't change these numbers and they'll change in the original array. It's a copy. All right. Any... We're doing good. Any questions so far? There have been a few already. Okay, let's start moving towards a slightly more physics-y topic. Sparse matrices. Pretty much all of the matrices, except in random matrix theory and in certain sick models, all the matrices you're going to bump into in physics are sparse. Okay? And something quantum physics. They are typically sparse to local Hamiltonians. Okay? What is a sparse matrix? Yeah, a sparse matrix is just a matrix where most of the elements are zero. Right? And you can sort of try to be quantitative. Oh, there's less than a tenth of the elements are non-zero. Who cares? If most of the elements are zero, it's sparse. All right? And let's take a look. So, actually, we can take a look at some of them. Just let me remind you, id is the identity, not my ego. Sx is the Pali x operator. Sz the Pali z operator. And sy is what? It's the only one I didn't explicitly write here. I happened to know the algebra of the Pali operators that allows me to write sy as minus i times Sz times sx. So, what is this? So, it's off diagonal, and there's some i's in it. And it's the minus one on the top or the bottom. Pali sy. It's on the top. That's on the test at the end of the summer school. Got to know the Pali matrices. Shouldn't have to look them up. Shouldn't have to think about it. So, this is what you are. So, now, suppose that I have a two-spin system. And the Hamiltonian, I feel like I have to write something analytic in this whole thing. So, the Hamiltonian is sx. Well, I'll put the x up on spin one. So, here's my spin and here's spins one and two. So, of course, that's a little bit trivial. I'll put the spin on two. What do we mean by that as matrices? Implicitly. There's a hidden tensor product, right? If you, actually, it's kind of amazing. So, how many people know that what this means is that there's a, if you want to represent it in matrices, there's a tensor identity here. So, that's the Pali sx and the tensor identity on the second position. OK. Raise your hands if that was, like, really new. OK. A few people aren't familiar with that. If you go to a math, like, go talk to mathematicians and you say, that's what I mean by s1x, you'd be shocked how painful it is to convince them that that's what you mean and that it makes sense. OK. But anyway, this is what we mean by sx acting on here. It means that that operator is on this tensor factor of the Hilbert space and that implicitly means that there's an identity acting on the other one. OK. So, that tensor, the identity can be represented in matrices in terms of what's called a Kroniker product. That's what it is. So, here is Kron sxid. So, how big should this matrix be? 4 by 4. And what it looks like is that. And what that is, if it's not clear is that I took the sx which was a 1100 and then I tensored the identity, which means I turn each of these into a block which is one times the identity. One times the identity matrix, 2 by 2 identity matrix, 0 times the identity matrix. And, of course, that's what is up there. Sorry, this is probably too small anyway, but you guys can hopefully see it in your Python notebooks. OK. How many non-zero matrix elements are there? 4. How many matrix elements are there? 16. 4 is a lot less than 16. It's a sparse matrix. So, anyway, we can have Python help us visualize that. We can spy on the matrix. Pretty, right? Oh, sorry. Ah, what did I do? Look at my code. What did I do? I shocked myself. This matrix is too big. I put another spin in there. Look, I cron the cron. Cron sxid, cron id. Right? Exactly where I put the parentheses in all those crons. But the chronicle product of sx identity identity is what it would be if I had a third spin. Here he is. 3. And that means I need another tensor identity. And that means this will become an 8 by 8 matrix, but it's still very sparse. And this is where the ones are from our figure. So, it's clearly inefficient if you're working with really big matrices, like come up with spin systems when n gets larger than 3. How big are they? For general n for spin half? 2 to the n by 2 to the n, which is a lot of matrix elements when n gets bigger than, I don't know, 4 or 5. It's already more than I want to write down usually. And if they're mostly sparse like came up in that example where there's actually only one non-zero entry on each row, then you're wasting both a lot of space and a lot of time by doing matrix manipulations in a full dense set of numbers, most of which are zero. If you multiply two matrices, you have row times column, and most things are zero, they just give you zero contributions anyway. So, why have the computer go to the trouble of doing that? So, what you want to use are sparse representations of that matrix. So, psi pi provides sparse matrix libraries. You don't need, and all the possible ways that psi pi can represent a sparse matrix for you. It's helpful to have a basic idea of how they work, but it'll do all the heavy lifting. All the bookkeeping is really what it's doing. Right? So, to import the sparse matrix libraries, we import psi pi dot sparse as sps. That's just to make it shorter for us to write, sps dot stuff. And there are a bunch of different sparse matrix formats that psi pi supports. They have a bunch of names. Compress sparse row, compress sparse column, diagonal, coordinate, dictionary keys, linked lists, and block sparse row. OK? These essentially are all different ways of just doing the bookkeeping of where are the non-zero entries and what are they. Right? So, a simplest one, to understand is coordinate. I store a big list of i, so for each non-zero entry I store in a list i, j and the value of that entry. That's not a very efficient representation to do computations with, but it's a simple representation to understand. It's a simple representation to construct. OK? The dictionary of keys is a pythonic way to do the coordinate representation using a dictionary where the keys are i, j essentially the same, but internally they're stored a bit differently. The actual faster representations for doing math with are the first three. Compress sparse row, CSR, compress sparse column, CSC, and diagonal. So diagonal obviously is only really good if you have a matrix which is essentially only non-zero near the diagonal. OK? Which comes up a surprisingly large amount. Maybe some other diagonals are non-zero, and everything else is zero. OK? Then the diagonal representation just stores a list of all the values on each of the diagonals which is non-zero. Right? The compress sparse row and column representations are a bit more complicated to explain how they store it. They basically tell you they store in order, compress sparse row in order reading from the top left of a matrix like you would normally read. Just what the non-zero entries are. So that's in a big vector. And then in another vector it stores what column they are in. And in a final vector it just stores a short list of at which positions the row number changes. So it's sort of like the first rows things are here, then the second rows things and so on. This is not a very easy representation to work with if you are doing the coding under the hood. But it's a very fast representation once you've got it set up to do matrix multiplies or matrix vector multiplies because you have row information stored contiguously like this and you want to multiply by a column vector. So that's a representation that you'll typically want to use when you actually start doing sparse linear algebra. The beautiful thing about Python is that you can construct in coordinate or dictionary keys or whatever is the simplest way for you to construct your sparse matrix or you can use as you'll see chronicer products with sparse matrices like we're about to do and you will get a beautiful sparse matrix and Python can then convert it to CSR for you, you don't have to do any work and then you can use that CSR representation to do your math. And which kinds of operations are fast with which kinds of matrices? OK, representations. OK, that's enough blah, blah, blah. So now we're going to create again just two by two palli matrices, identity S, X, Z and S, Y, but we're doing it with this sparse.i sparse.csr matrix of the S, X matrix and the S, Z matrix and their product. So all I did was use this SPS or sparse library version of the identity constructor here I created a normal array because this was such a small one anyway sometimes you don't want to do that but in this case it's not a big deal and then converted it to CSR using this OK? So if I try to look at the identity matrix what am I going to see? What I'm talking about nobody knows my... So I'm not going to see the identity I'm actually just going to see this little stub which is telling me it's a two by two sparse matrix of type float with two non-zero entries, two stored elements in this case in diagonal format I can see the actual sort of dense array by calling two array and this returned to the normal array type. You don't want to do that with a really big array because it will create a lot of zeroes it will take a lot of memory if the array is really big but it's nice for these small ones here is another warning there is another method called to dense these are called dense arrays as opposed to sparse arrays that returns the matrix type instead of the array type and it is only there for historical reasons and like I said, don't use the matrix type so don't use to dense it will literally only introduce bugs and make you cry I know, I've cried over them Yeah? OK, good Sx this thing, it's smart enough even though I handed a dense array of floats it was smart enough to realize that it wouldn't be stored and so it stored this in CSR format, compressed sparse row format sz similarly sy there it is another CSR all of them actually only have two non-zero entries we can look at it and indeed the minus i has stayed on top as it should OK, so now let's start doing some physics let's consider a three site transverse field ising model, ising chain with periodic boundary conditions the operators as I mentioned over here the operators sigma i or si are implicitly tensor products with the identities on the other side so sigma zi, say sigma z1 is got an identity on the left and an identity on the right because there's an identity on the zero spin and an identity on the two spin chain and I hope it's reasonably familiar but this is just a chain of three spins which are coupled by a ferromagnetic zz interaction if j is positive it's ferromagnetic it wants to make the spins point in the same direction and then this is a transverse field so a field pointing in the x direction transverse to the coupling so how do we deal with this so sx acting on spin zero in a two site chain would be this chronicle product sx id and notice I used sparse chron because I was passing it to sparse matrices and I wanted to get out a sparse matrix and it is actually giving me a 4x4 sparse matrix of floats with only eight stored elements which is exactly what I was hoping for if I look at it it is the matrix that we met not long ago ok now zero in three which we also did densely just a few minutes ago so this is sx acting on the zero spin out of a three site chain I need to chronicle product an additional identity onto it there it is but at the intermediate step I put that to an array there so we could see this sx identity twice but it is actually stored sparsely you can imagine that if you have n as a parameter the length of your chain you need to keep tensuring on these identities you don't want to have to do that by hand by writing chron it is kind of the thing that computers are good at doing the same thing over and over again right so what we are going to do is we are going to figure out a way to construct sort of the general case of an operator which is a chronicle product so here is one way to do it so we are going to create a list this is a pythonic list of three operators that are two by two sx identity identity those are all sparse matrices but that is fine there is my list it is a list of sparse two by two matrices then here what I am going to do is this is slightly tricky first I set sx zero to be the first element of that list so it is going to be set to sx then four op in op list from one to the end chronicle product on to sx zero that operator and then assign it back to sx zero so what does this do that created what I wanted to create and how did it work well essentially I just used this iteration over the identities here and on each pass through the iteration chronicle product on to the previous iteration output of the previous iteration sx not and then I assigned it to sx not which is why it grew on each step so this was now I could easily change this to act on say a foresight chain I just make this list one site longer and the same code now created a really big array ok now for those of you who like functional programming techniques here is a more compact way to write the same thing if you don't know what reduce means then I'm not going to explain it right now but the people who know what reduction by a function is will understand this line I'm just telling you you can do it you can ask me after if you really want to know I don't want to explain it right now ok this effectively does the exact same thing that our for loop did and indeed we got the exact same output now let me go back to the case where this was only three like that and I'll run that and I will check indeed I have an 8x8 array good so this code is still maybe not the most convenient I should wrap it up in a function to do this but actually I wanted to leave that as an exercise for you guys so what this does is it's creating a Hamiltonian which is sx acting on the zeroth site this reduce sps chron of this list was the thing that put it sx tensor identity tensor identity plus same thing identity identity sx ok and the bond terms are quite similar they are sz tensor sz tensor identity so there is an sz on 0 and sz on 1 and identity on 2 for the first term then there is a identity sz sz and an sz identity sz this is the periodic term on our little periodic chain and so this line illustrates that here I'm adding and it's kind of too long for it all to go on one line so if I want to have a multi line command in python I can put a backslash in the end and then keep going so I'm going to execute that and now I have these two 8x8 matrices so we can take a look h field is 8x8 it has some number of non-zero elements and we can look at it and there it is it's got ones and some funny locations that would have been hard to sort out by hand but the computer did it for you and now let's actually construct a Hamiltonian I've here set j equal to 1 and the transverse field equal to a half in units of j that line probably looks kind of like what we would have written mathematically h bond is that sum of those bond terms in h field terms and we can just check by spying that that final Hamiltonian which had both terms in it actually has this sparsity structure so it's still very sparse which is why it's useful to use sparse techniques now having constructed it dense I just wanted to show you how to diagonalize so now I'm creating a dense version of the Hamiltonian and I'm calling the numpy.linalge this is a numpy linear algebra package igh subroutine which is the Hermitian eigensolver I give it the Hamiltonian and it returns for me two things e and u e is the energies how many should there be 8 it's an 8 by 8 matrix I just diagonalized it so there should be 8 eigenvalues and u is the unitary which diagonalizes it or alternatively the columns of u are the eigenvectors the eigenstates of h which means in particular that if I take the Hamiltonian and I multiply say the first column I should get the energy of that eigenstate times the first column back right so what will this line give me what will I get so I'll just get the eigenvalue let's see so what was that so the ham dense at u,1 pulled out the first eigenvector because they're column vectors of u I acted on it because it was an eigenvector it should have been minus 3.14575131 times the column vector but then I divided and of course division is element wise so I divided that by the eigenvector itself so I get a vector of 8 entries each of which is the eigenvalue now we can plot the spectrum for a little size 3 chain on the x axis I've put the field of course we've only evaluated this at one field value the y axis I've put the energy and I put a little x at each of the 8 energy locations and does anybody know what how to physically interpret this weird looking data there's two states that are very close together near the bottom then there's a bit of a gap and then there's a bunch of states up here was that so we're in the ferromagnetic phase normalizing chain and there is an almost degenerate pair of states that roughly correspond to magnetization up and magnetization down and then there's a gap to the excitations which are domain wall like excitations of that chain and they cost finite energy to create and so there's a gap so magically physics came out of some sparse matrices it's not a numerical error numerical error for these small matrices is completely to forget about that's because it's only a finite length chain the degeneracy is split exponentially in the length of the chain so it's e to the minus l over the correlation length which is something and that's just not zero when l is only three good so that's a finite size effect that they're not exactly degenerate though a very small one so we did that using a full l over because an 8 by 8 matrix is actually really small so we might as well do it dense we created them sparse and if we went to bigger systems we would need to use sparse diagonalization techniques I'm not going to explain how they work, I don't know if somebody else at this school is actually going to talk about so you guys, if you don't know what a Krylov technique is or Langshaas you won't learn here but you will learn how to use it and you can read about it so in the last representation of a matrix there are iterative techniques which don't require working on the entire Hamiltonian to get the eigenspectrum or parts of it they are wrapped up in sci-pi sparse.lin-alge so I've imported that as sps-lin they work just like the non-sparse solver, they just have a different name sps-lin.ixh that s is for the sparse there's my sparse Hamiltonian ham the more it tells the solver I only want four states so sparse techniques won't give you the entire spectrum they'll only give you some number that you request and the more you request the longer it will take and then the which here is telling the solver I want the smallest algebraic ones that basically means the bottom of the spectrum because you could also ask for the largest ones or the ones nearest zero or whatever so you can read the documentation for exactly how that works so we'll return the bottom four energies let's see do they match 3.232 and my 3.232 3.145 3.145 et cetera so we got the bottom four from this that was actually a wrapper for a numerical package called laypack and rpack so they'll be well optimized mature systems so they'll work for most purposes though there are fancier ones available if you want to parallelize and so on so we got the energies look I even put it there we can compare right next to each other these agree to as many digits as it is displayed between the dense diagonalization we did a minute ago and the sparse one that we did into e2 now what's faster what do you guys think we can put them head to head with this time it thing took 19 microseconds to diagonalize in 8x8 matrix and do you guys think the sparse one is going to beat it who thinks the sparse is going to win who thinks the dense is going to win is anybody taking money no look at that, that horrible, that's terrible that's like a factor of 20 slower so the sparse iterative technique the technical term is sucks if you're trying to get the spectrum of a very small matrix don't waste your time, just do it dense but there's going to be some point when the matrix is big enough and sparse enough where the sparse technique will be faster and there'll be some point where you can't even do the dense technique because the matrix is just too big to hold in memory and also you can gain a little bit there's a version of both the sparse and the non-sparse eigensolvers which doesn't return the eigenvectors to you and if you don't need the eigenvectors then don't ask for them because you save a little bit of time by not constructing them so this eigenvals age as opposed to eigen age gives us just the energies and they are the correct energies and if we compare the timing so for a slightly different notation for the dense one you call eigenvals age for the sparse one and how they don't return the eigenvectors all of this is in the documentation shift tab and if we compare 13 microseconds to sorry it's scrolled off 19 we actually gained a fair bit I mean not that 6 microseconds matters that much to most people but in this case anyway so that's non-trivial because we didn't bother to return the eigenvectors and the sparse one was not save 20 microseconds out of so it's not a big savings but if you don't need it don't construct it and also it takes less memory little bit now this code is going to diagonalize in steps at all these h values from 0 to 4 in steps of 0.1 it's gonna diagonalize construct that Hamiltonian and diagonalize it and store the energies in the array that I've prepared in advance for the purpose and then we'll do that that was fast and then we'll plot it and there is the spectrum of the three site transverse fieldizing chain as a function of field that's energy and field and where is the phase transition what's that so we can see it over here at small transverse field the system the ferromagnetic term is dominant and we see that there's a very good degeneracy between the bottom two states which were those all up and all down states roughly and when we go to very big transverse field the transverse term is dominant which means that the ground state basically instead of wanting to look either up or down wants to look left and that's a unique state so this is the paramagnetic side and that's the ferromagnetic side the actual transition in the transverse fieldizing model by exact solution at whatever infinite thermodynamic limit is at 1 and what you're seeing this little splitting here that happens a bit early is finite size rounding of the transition in principle the gap here should also close and you can't see that at all in finite size 3 numerics because it's too small to see the gap to the quasi particle excitation but if you go to bigger systems you might be able to see it now I didn't explain this code in detail but I'll let you read through it I think we've covered almost everything in it and in the last half hour or however long you want to sit here I've written here a whole bunch of tasks for you guys to do ok and this is like the interactive wake up do stuff part of the of the lecture so generalize the code to n sites and make a similar plot for n is 4, 6, 8 and 10 and my hint and this is why I didn't do it for you actually is to write a function make sx in which returns the action on site i in an n site Hilbert space so the right identities chronicer product did onto it and similarly a make sz term i and j so action on i and j in this code you'll usually want it to be i and i plus 1 but it's periodic so you might want it to be n minus 1 and 0 and once you have those functions it should be very easy to generalize the code and then you can think about how much space these require and at what size if you can do all this you can actually see just test it what size does the diagonalization become faster by the sparse techniques than the dense ones once you can construct it all but really the physics thing is to go to larger sizes once you have the code to make this general and replot this and see how it looks as you make the size bigger and see if you can see the phase transition sharpen into a phase transition so first person to measure the scaling exponents of the transition gets a beer i'm here, you can ask questions i'll wander around i'm sure there's also some other pythonistas in the room who can help what's that? that makes it hard so the jupiter server is not working? how many people have a local copy running? there's a few people okay you can all crowd around that i'm sorry, i think we need to upgrade the server somehow it seems like 100 people connecting to it blows it up there are there are some publicly available servers and you can copy and paste some of the code into that if you want to play with it they're test servers, let's see wrong website jupiter it's not that hard to install jupiter on your system of anaconda if you want to and maybe i should encourage more people to try or get your friends to help you do it if you're not confident about how to do that so that more people can do it locally on their machines for tomorrow use anaconda, just do the default stack unless you have a reason not to do that i mean all the things say do it fancy ways if it works, it works it is a mess so actually this is a good point the hardest part about using python is actually just getting the full stack installed properly on your computer and it's much easier now than it was a few years ago but it's still a pain the easiest way to do it is with anaconda and follow some directions online like step by step for which pieces to install anaconda should just do it for you i typically install it i am using pip in a more manual installation but it's because i know exactly what i want there are some people around the room who clearly know how to do this and they might be able to help i can help if somebody gets wedged does it kill rabbits most anacondas do eat rabbits yes your whatum? oh adam no, shouldn't so i can use both simultaneously in principle yes it clobbers it yes it clobbered adam because you tend to get multiple references to different pythons that's the problem let me