[MUSIC] Hi and welcome again to the fifth week of our class, Simulation and Modeling of Natural Processes. This module will be about the streaming step in the Lattice Boltzmann method. Let's remember again that streaming takes the population from the out coming state of one cell to the incoming state on neighboring cells. By doing so, it maps post-collision values, which are the f out or red arrows, to pre-collision values which are the f in or green arrows. Streaming also takes our system from a given time t, to the next time t + delta t There's nothing more to be said here. Let's go right away to a Python code which does this. There is one technical problem by implementing the streaming step, which is why I will start again with a for loop over both x and y directions of space before going to the new pi array-based syntax. This complication is that you have to handle separately populations which are on our domain boundary, and which are streamed out of the domain. You have to do something with them. What he chose to do in this code, which we are developing today, is to take them, stream them out of the domain and re-inject them into the domain in the opposite direction. In this way, we have periodic boundary conditions, everything which leaves things to domain on the left, enters on the right. If it leaves on the right, it enters on the left. If it leaves on the top, it enters on the bottom, and vice versa. We call this periodic boundary condition because the system behaves as if it was periodically repeated an infinite amount of times in the direction in which it is periodic. If it is periodic in the x direction, it is as if you had an infinite amount of system arranged, which are all the same, and which are aligned in the x direction. In y direction, means the same in y direction. So how do we handle this in the code? We start with a for loop with both stays base directions, and then for every direction, for area of one of the nine directions, we calculate the coordinates of the neighboring cell. If the coordinates of the neighboring cell, which is just the current coordinates plus the Lattice velocity according to the given space direction. If these coordinates gets out of the system, which means if its x component is smaller than zero or bigger or equal to nx, or if its y coordinate is smaller than zero or bigger or equal to ny, then we have to put it, periodically, to the other end of the system. If it gets out at the zero end, we put it back to the nx or ny end. If it gets out of the nx or ny end, we reinject it at the zero end. We explicitly implement periodicity in this way and then streaming is nothing else than a copy of outgoing populations to the incoming populations of the neighboring cells. You can do the same thing with new python rabie syntax, although it is a little bit more difficult to understand. But easier to write and much faster to execute. NumPy offers a function which is called roll which will shift a matrix periodically in one of the space directions. To do the streaming step, we will have to call the roll function twice, once to shift it by the right amount of cells in x direction and once to shift it by the right amount of cells in y direction. The number of cells by which it is shifted is either 0, 1, or -1, and it's given by the value of the Lattice velocity indicating space direction. So the NumPy array-based code for the streaming step is nothing else than a loop over nine space directions, a double call to the roll function of the outgoing populations. As a parameter, axis 0 for the x direction, axis 1 for y direction, and the amount of shifts given by Lattice velocities. And we copy the results back into the incoming populations to have them ready for the next collision step. Before we end this session, I would like to make a short comment about the fact, about the question, if really we need to allocate so much memory. We have allocated a huge matrix which is of size 9 times nx times ny. Once for the incoming populations and one for the outgoing populations. That's a lot. Couldn't it be possible to just allocate one set of populations and store both the incoming populations and the outgoing populations into the same set of variables? The answer is yes, this is possible. It's not even difficult to do. We will not do it today because it makes the code a little bit more tricky. And I prefer to have a code which is easy to read than really efficient in terms of memory management. But I will anyway, if you are interested in this topic, show you how to proceed if you want to store the particle populations into just one matrix. What do you need to be aware of? When you do the collision step, you take the incoming populations and compute locally density, velocity. You compute equilibrium, you do the collision, and store the result in the outgoing populations. In this case, because everything is local inside a single cell, there is no problem. Once you have computed the collision, you can store the result, the outgoing populations, and overwrite incoming populations, you don't need them anymore. There's nothing to be thought or changed here, you just do it, and store both incoming and outgoing populations, in the same matrix. But during streaming there is a slight complication. During streaming, you take populations and copy them to neighboring cells. If you're working with just the one set of matrices, then by streaming populations to neighboring cells, you might overwrite a population which you still need and lose information. However, if you think about this carefully you will see that this is not a problem at all. Let's look at the streaming step, and let's look at two populations, one which from the left cell is propagated to the right cell and one which from the right cell streams to the left cell. Let's watch the motion at the same time. So both of them go from outgoing red states to incoming green state on the neighboring cell. The interesting property is that they occupy a position on the neighboring cell, which just has been vacated because the corresponding population, which is of opposite direction, also left this cell. The trick, if you want to work with just one set of populations, is to not stream particles from one side to the other one, but to exchange them, to do a swap. If you take a population on one cell, you identify its neighboring cell. Identify the opposite population on that neighboring cell and then you just exchange the two values. Instead of overwriting anything. By doing so, it's easy to do a Lattice Boltzmann model with just one set of populations into which you'll save both incoming and outgoing populations. But as I said, we will not explore this property today to keep the code as simple as possible. Just for your information. The same property also holds for diagonal directions. I show you here what happens if you take the lower left cell and take the f zero out of population which, as a matter of fact, it is the F8, the opposite population, which streams from the lower left cell to the upper right cell. And in the upper right cell it is to add zero out population, which streams on the lower left cell. Again, you can do this streaming by just exchanging these two values and avoid overwriting any population. This is the end of our module on the streaming step in the Lattice Boltzmann method. Stay tuned for the next module. [MUSIC]