 Okay, so thanks for the reminder. So now we're recording. So there are some logistics for this meeting. So the first thing is the repository that we're gonna use for all the code and for the notebooks that we'll present in the clinic. So let's go there next. Okay, so you can see the URL here. This also came out to you in the email that I sent out yesterday, I think it was. All right, so let's visit that webpage. All right, so here it is in my browser. Just a quick note, I reversed the colors on my screen to make it a little bit easier for me to see. So this will probably have a white background for you guys. All right, so here's the repository that says basically all the information we're gonna need for the clinic. So I wanna point out a couple items on this just for logistics. All right, if I scroll down in the read me, you can see that there's a rough agenda for what we're gonna cover in the next two days. So today we'll talk about LandLab and then tomorrow we'll talk about BMI and PMT. Note also, we talk about the CSDMS Jupyter Hub. This is a great thing. It means that we can run our notebooks online and that you guys don't have to download and install stuff on your local computers. So I sent out information yesterday about signing up and logging in. So I've also gone through and authorized everyone that I could find before the meeting. So if things aren't working for you yet, that's okay. Just send me a chat message and once I turn this back over to Greg, I can help you out. Okay, so one cool thing that I wanna show you, if you haven't tried this yet, but this is a really neat thing, is on this repository page, you can see underneath the agenda, there's a link underneath the day one information. If you click this link, it'll bring you to basically a login prompt for the Jupyter Hub and it will then provision all of the notebooks that Greg will be using today. So I'm gonna cross my fingers, I'm gonna try it really quickly here and see how it works. All right, so I click the link and there we go. So now I'm on the page that shows the notebooks that Greg will use today. So I'll let Greg explain about these later, but this is kind of how it'll work for you guys as well. And note that it may not be instantaneous. Arena and I did a workshop at AGU where it took a couple of minutes just because everyone clicked the link at the same time. So it may take a little bit longer for you, but that's okay. All right, so the last thing, as far as logistics are concerned, if you have any questions or need any help as Greg goes through his presentation today, please use the Zoom chat. You can either send a message to everyone or send a message just to me and I'll try to triage any of the chats. I can try to answer questions there. If your question requires a bit more in-depth help, I will try to set up some Zoom breakout rooms and I'll have a breakout room for me and for Eric and for Arena and maybe I can put you in one of the rooms together with one of us and we can help you kind of more closely as well. The only drawback with that, of course, is that you're kind of missing Greg's presentation. But we can at least give it a shot. I'm a little new to Zoom as well, so I think I can do this, we practiced last Friday, but I think it'll work. Okay, and also then we're recording. Okay, so I think that's all that I have for the logistics today. So thanks again for signing up for our clinic. Welcome, and I hope you have a good time. I know that I'll be enjoying watching Greg's presentation. All right, so I'm gonna stop sharing and Greg, I'll turn it over to you. Awesome, thank you, Mark. So I'm Greg Tucker. I'm a director of CS2DMS and involved in the Land Lab Development Team and what we're gonna do today, you're all share my slides with you. I'm gonna give what hopefully is not too long an overview presentation about what Land Lab is and what it can do. And then we'll spend some time looking at the systems Jupiter hub. I think Eric is gonna walk you through that. And then we'll come back together and I'm gonna lead you through one, or if we have time, two notebooks that give a little bit of a hands-on feel for how to use some of this stuff. And hopefully you'll be able to follow along in the notebooks access to the Jupiter hub and I've even sprinkled some exercises in there for you to try some things out. So we'll see how this goes. So I'll start with a little bit of motivation. You know, in the sciences that deal in one fashion or another with the surface of the earth or even other plants. Of course, part of what we do is we use numerical models as a way to interrogate our understanding to make testable predictions and even to do operational work and applied work. And it's interesting to observe that from a sort of computer science standpoint, people in very different fields are in many ways solving the same problems with software. And this little slide here gives a few examples that I've pulled from the literature of different contexts in which some kind of a two-dimensional that is spatially distributed numerical model is used for some problems. So from the upper left, we have catchment hydrology and runoff in the upper middle soil erosion in the upper right, glaciation. And here's a big change in scale from a single agricultural field to a large chunk of the Sierra Nevada's lower left, landform and landscape evolution and sedimentary systems, lower middle, another planet, Mars and a simulation of impact cratering and erosion on early Mars. And then finally in the lower right, volcanic flows. So you can imagine each of these papers appeared in a different journal and community community. And yet when you think about the kinds of tasks that the authors had to perform in order to do this work, they're actually pretty similar. So they're all constructing some kind of a grid that represents space. And they're all dealing with inputs and outputs of that spatial data. And they're all thinking about how to route some kind of a geophysical fluid across that trained surface, whether it's water or ice, soil or sediment or even lava, right? So there's some interesting commonalities behind what seemed initially like really different kinds of problems. And it was that observation and it's part of what motivated us to develop this programming library that we call LandLab, which is really designed to speed up and make more efficient the process of creating this type of model by creating tools for some of the most common operations and allowing you to take certain chunks of functionality and encapsulate them in reusable software elements called components. So that gets us to LandLab. So LandLab then is fundamentally, it's just a programming library. It's a programming library written in Python and designed explicitly to make it easier, more efficient to make these 2D grid-based models. It's geared toward, as the name suggests, Earth-surface dynamics, but it's not necessarily limited to that. And in fact, one of the things that we worried about in giving it the name LandLab was that it might alienate people who want to do things under the sea or having to do with the sea. There's no reason you can't do that. But at any rate, it's a tool. And it's a good compliment to the systems Python modeling tool, which you're gonna learn about tomorrow. So there's really kind of three key pieces to LandLab that I wanna point out in this overview. And the first has to do with simulation grids. If you've ever tried to write a two-dimensional numerical model, you probably discovered that setting up a grid can be a real headache, even if it's a pretty simple grid like just a rectangular raster arrangement of cells. There's boundaries to worry about. There's how do you index things properly and so on. So LandLab tries to take some of that pain away by having grids that are objects. So if you're not familiar with object-oriented programming, you're gonna dabble a little bit with it today. So grids are data objects. They use flat arrays to represent data. And data are organized into what we call fields. So a field of data is really just an array that represents values of the thing you might care about, temperature, soil moisture, water depth, land surface height, you name it. Any of those things would be represented as a field, which is just a 1D array of numbers, whose values are associated with particular grid elements, and therefore with particular locations in space. That design allows LandLab to have multiple different grid types using basically the same data structures. And the current grid types you see in this image here, there's raster grids, hexagonal grids, and irregular Voronoi-Delone triangulated grids. There's even a sort of a special case of the latter called radial grids. LandLab also has, as you'll see, some built-in numerical functions to do things like calculate the gradient of the slope of a quantity. It's the slope of the land surface or the slope of the water surface or something like that. So that's grids. And just to illustrate a little bit what that looks like, here's an actual line of Python code that creates a new grid. It's a grid object, in this case, with 10 rows and 16 columns. And that line of code creates a data object which can be depicted graphically like this. And I show this because I wanna point out that grids are composed of basic primitive geometric elements. And the three that we're gonna work with today are the nodes, which are the black points here. These are often the points where you put data like land surface elevation. There are links. Links are line segments that connect adjacent pairs of nodes. So one link will connect two nodes. And there are cells, which are these, in this particular case, square patches that surround a grid node. So that's an example of a raster grid. Here's an example of a hexagonal grid. So hexagonal grids have a bit of a different arrangement, but you could see they too are made of nodes, links, and cells. Here's a variation on that same idea, but with a rectangular node layout. And here's an example of a radiomongue grid where there's some irregularity in the grid structure. So that's the idea, is that you can create a grid of desired size in just one line of code. So the next idea is the concept of a reusable component. So the idea is, let's say somebody has written something like a way to calculate shallow overland flow, which is an example hopefully we'll get to later. And you would like to reuse that, but you wanna add something to it. Maybe you wanna add vegetation to it as a source of roughness, or as a, you want your model to feed crops, or maybe you wanna add some things having to do with soil moisture or something like that. Well, if somebody has already written a numerical solution to shallow flow across the terrain, it seems a waste of time for you to have to rewrite it if they've done a good job of it. So that's the idea, if they've written it as a reusable component, that you could build a new model by taking their component and combining it with whatever components you want, together with a grid, and whatever fields of data you need, water depth, water flow, velocity, whatever else there might be. And that collection of things together makes a new numerical model. So this little diagram here is really sort of a graphical depiction of what is in reality, a little Python script. All right, so that's the idea of a component. The Landlab itself has already a collection of a few dozen components that various folks have written, and they do all sorts of things. There's components for soil creek, there's components for channel incision, there's components for generating random storms, for landslide probability, for flexure of an elastic lithosphere, for growth of vegetation, and a bunch of other things. I'll just briefly mention that Landlab has a little cellular automaton modeling toolkit built into it, and here's a couple of examples from that. We won't be exploring that in this tutorial today, but just know that that's a thing if anybody is interested. And then the third element really of Landlab is a collection of utilities that takes some of the pain away from doing common chores that you need to do, like input and output parameters, or of gridded data, and that sort of thing. And hopefully we'll get to see an example of where we read in a digital elevation model in an ASCII format. So that's the quick overview of Landlab and what it contains. This slide is trying to make the point that you really can do some things that normally would take a long time in a smaller amount of time. This is a little uplift and erosion model of a rising fault block where we are representing the erosion and deposition process using a diffusion model. Many of you would be familiar with the geomorphic diffusion concept. And the loop that runs this model ends up being six lines of code. And in fact, this is one of the examples that we're gonna explore together in a notebook. So if you learn a little bit of how to use this library, you really can begin to put together some simple models fairly efficiently. Okay, so let me talk a little bit about the example. This indeed is the example we're gonna work with most of this afternoon. And the idea is here we have a fault, a tectonic fault, and there have been one or more earthquakes on that fault that have raised up one side of the landscape. And at the same time, the sort of sharp edges of that landscape have been smoothed off by the transport, erosion and deposition of soil to create something like the fault scarf that you see in this picture. So this is our modeling target. What I'm gonna do next is to walk you through some of the theory and math behind the model of the bill as well as the numerical setup so that when we actually get to the notebook, you'll have some sense of what the notebook is trying to achieve. So we'll start with the math. We have two governing equations in this simple demonstration problem. The first here is a conservation of mass law. Here the symbol eta is meant to be land surface height at a particular point in time. And q is the soil flux or sediment flux defined as volume per time per unit width. And we'll take a closer, we'll revisit that definition again in a moment. So that's conservation of mass. The second equation is a rule for what governs the soil flux downhill. And the rule is really simple. This says in math that the flux q is equal to the slope gradient times minus d. So the minus sign says soil flows downhill and d sets the rate at which it moves. Now, let me pause here and point out that some of you may be very familiar with this math or this style of math or others that may be new. If it's new to you and you wanna learn more, one of my favorite references is a book called Mathematical Modeling of Earth's Dynamical Systems by Slingerland and Kump. I think it's out of print now, but you can still get copies on the various mail order sites. So, okay, we can rewrite those equations in what's maybe a slightly more familiar way by breaking up the vector components. And they look like this. So here in the first equation, the left-hand side dA to dt is describing how fast the land is rising due to deposition or if it's negative how fast the land is lowering due to erosion. On the right-hand side, you have two derivatives, one of the x component of soil flux and one of the y component of soil flux. It's a two-dimensional problem. And in the lower two equations, we're looking at just breaking up the two vector components of soil flux, one that describes the x direction, think east-west, and the other that describes the y direction, think north-south. So those are the equations that we want to approximate numerically. And to do that, what we're gonna start by doing is to imagine that we have a world that's made of square cells looking something like this and each cell is gonna be delta x in the north-south direction and delta x in the east-west direction. So we're looking down on this patchwork of cells. And what we're gonna do is to keep track of the volume of soil inside each cell. So we'll track how much soil enters each cell, how much leaves the cell and how the cell's elevation changes as a result. We're gonna place inside each cell a point that we'll call a node. And we'll also have, and the nodes are where we're gonna actually keep track of the land surface height. So we'll also have a ring of nodes around the edge of our squares, our landscape of cells. And I'll refer to these as boundary nodes. So all told in this example, it's like we have a five row by seven column grid. Here's the thing that'll be useful to us later. The numbering is gonna go from lower left to upper right like this. So bottom most row is zero, one, two, three, four, five. The next one is six, seven, eight, nine, 10, 11, and so on until we get up to number 29. That's how we'll set up our grid. Next, let's revisit for a moment the definition of this thing called Q, which again represents the soil flux. And formally it's defined as the volume of soil flux per unit slope width per unit time crossing, in this case it'll be crossing a cell phase. Because it's volume per time per length, the dimensions work out to be length squared over time, or in this case we'll think in terms of meters and years, so meters squared per year. We'll have a convention that says Q is positive if soil is flowing to the east or to the north, and it'll be negative if it's flowing to the west or to the south. Okay, so that's our definition of Q. Now we can think of how to operationalize the soil volume balance on our cellular landscape. Well note first of all, think about how much volume of soil inside a cell you have. Well, if the cell's height is eta and its surface area is delta x by delta x, or delta x squared, then the bulk volume of soil would be eta times delta x squared. So that's the volume quantity. And we know from our basic equation that the rate of change in that soil volume at some particular cell, say I, should be the sum of all the inflows and outflows across the cell's four phases, right? So you can imagine having a little gate guardian at each cell phase who's keeping track of how much soil is flowing in or flowing out every time you're in it. So okay, one of the things that means that suppose I have the west phase of a cell, if the inflow rate per unit width for that phase is Q, then the total volume inflow rate is inflow rate is that times the width of the phase that is flowing across or delta x. Okay, we'll see why that matters with a little illustration. So here I'm looking down on a particular cell called number I, and it has a height eta I that we've recorded at its node. And we're interested in keeping track of the soil that's flowing in from the west side. So we'll call that flux Q west and the total soil per year would be Q west times the phase width or delta x. Similarly, we can keep track of what's going out to the east coming in from the south and going out to the north. Put all those ingredients together and here's our mass balance equation that we're gonna try and solve iteratively. We have on the left the derivative of volume of soil with respect to time. And on the right, we have each of those four inflows and outflows in from the west and south out to the east and north. Well, we can rewrite that by dividing both sides by cell area delta x squared. And this becomes our discretized equation for the rate of erosion or deposition at a particular point I. Now, next thing we need to do is to think about how do we actually calculate Q in this gridded world? Well, here's how we do that. Let's suppose we're looking at the west or left-hand face of a cell. Again, we're looking down on this gray cell and we wanna know what is the flux across that face. Well, we know that the flux should depend on the gradient at that location. That is the surface slope times the coefficient D. So here's what we can do. We can calculate the gradient as the difference between the height of node I here and the height of the node just to its west and divide that by the distance between them. That gives us a measure of the gradient. Multiply that by minus D and that gives us Q west. Now, there's a natural way to do that with a land lab grid. We can take advantage of the fact that there's a link that connects this point with this one. So we can calculate the slope at that link as this thing here, this difference. And we can assign that number to this link where we can think of it as living right in the middle of the link there. So think of the links as a natural home to slope. And if we store the slope there, we can also store the flux there because the flux is just slope times this parameter D. Okay, so that's sort of a quick introduction to our numerics. The last piece we need is to think about how to advance in time. And here we'll use what's often called just a forward difference approach. That is to say we will represent our continuous time derivative D A to D T as a discrete derivative. Here the superscript represents the time step number. And so we have elevation at a future time T plus one minus elevation at the present time divided by a delta T. Okay, that's not a completely thorough and exhaustive breakdown of the numerics and we could drill into this if anybody is interested, but that hopefully gives you at least a little bit of the flavor for how this is gonna work. So what we'll do from here is jump right to a notebook that tries to implement this setup using LandLab. But first we're gonna get an introduction to the LandLab Jupyter server. So I'll turn it over to Eric at this point. Thanks Greg. That was great. And thanks Mark because you mostly covered everything that I was going to cover for getting people started on the Jupyter notebooks. I totally did that on purpose, Eric. But maybe some people missed it and so I'll go over it quickly again. So we'll start, if we start at the, I'll share my screen here. I'm gonna try to, am I sharing my screen yet? No. There. Yeah, that's it. Is that better? Yeah. All right, so we're back at our GitHub page which is where all the materials for this workshop are going to be. And if you scroll down a little bit, you need to create an account which is pretty easy. You just have to enter a username and any password you like and then Mark will authorize you to be able to use our Jupyter Hub. And it looks to me like everyone has already done that. But if you haven't, you're gonna have to do that first. Then the easiest way to get all the materials for Greg's tutorial is to simply click on this link down here that says get the workshop tutorials for part one. So if you click on that, it will take you to the login page or if you've already logged in, it may just log you in automatically. It will then clone the repository and put you in a, I'll do it right now. So it's cloning the repository and then you'll get the latest version of Greg's notebooks that you can then click on and you should be able to run through them. As Mark said, might, because everyone's gonna be clicking on these links right away, it might take a little while for the notebooks to download onto the server. But hopefully there's not too many and it shouldn't take too long. Is everyone to this spot? Hopefully, yes. Oh, I see a thumbs up cream. So I'm gonna take that as everyone's logged in. I mean, now you can just click through the notebook. And so I think, so we'll leave up the server here for our clinic. So it's a pretty beefy server right now. Hopefully it can handle everyone working on it at once. After the clinic, we'll keep it up and people can still log into it, but we'll probably reduce the size of it. So it might be a little slower, but you should still be able to use it for some time after the clinic. But for the clinic, I think the easiest way for you to log in is to go to our GitHub page for the workshop and then click on those links and then we'll have different links for the day two of the meeting. But if you'd like, you can log into it directly in one Python from there. So I think that's really all I was going to say about the Jupyter Hub and just make sure that everyone could log in and things were working okay. And if that is the case, I guess Greg, I can pass it back over to you. Sounds great. Yeah, yeah. I will stop sharing my screen. All right, I will start sharing mine. Okay, so hopefully everybody can see this notebook. So what we're going to do is run through this notebook a bit at a time. And as I mentioned, I've sprinkled this with some exercises for you to try out as we go. We'll probably spend most of our time on this one notebook, which is a geomorphic example. There's a second notebook that does a hydrological example, a little overland flow component that we can play with. And I wanted you to see that so you can get a sense that LandLab isn't restricted to geomorphic things. It's just that some of us are geomorphologists who put a lot of effort into that side of things. So okay, what we're going to do in this notebook is initially we're going to try to build a numerical version of that setup that I described. Now this notebook starts out with a part, a little section that walks you through how you would build a one-dimensional hill slope evolution model without using LandLab, just using NumPy. And in the interest of time, I'm not going to go through this. I'll simply skip over it, but know that it's theirs for your enjoyment. Instead, we're going to start here in part two, which is building a 2D diffusion or a hill slope evolution model using LandLab grids. And I'll describe a lot of what these lines are doing. Some of them will skip over since they're a little bit tangential to the key points that I want to get across here. The first thing we're going to do is to import a raster model grid. So here the name raster model grid refers to a class. So it's object-oriented programming. And a class is really a description of a type of data. It can be a very complicated type of data containing many different pieces of data, and it also contains typically functions that are attached to that particular class. Okay, well, the next thing we're going to do is to create an instance of that class called an object. So if you think of dog as being a general class of animal, you know, fluffy is a pillar of a dog. In that same spirit, what this middle line here does will be to create a new object. We'll call it grid. There's nothing special about that name. I could call it fluffy or anything else. And in this particular case, it'll be a grid that has four rows, five columns, and with a spacing between the columns in the x-direction of three units and a spacing in the y-direction of four units. And then we'll use this little built-in function plot graph to make a plot of the geometry of the grid we just created. So here it goes. Okay, so here's our little four by five grid. You can see the numbering of the nodes, which are shown as these red points goes from zero all the way up to 19. And that the spacing is a little bit longer in the y-direction than in the x-direction. Okay, great. So we've created our first grid. The next couple of lines walk us through how we can inspect different grid elements. So I can use this plot graph function to look at the links. Remember links are these line segments that connect pairs of adjacent nodes. And in this case, for example, link zero is the link that connects node zero to node number one. Link six is the link that connects node two, node seven. One thing you don't see here is that the links actually have a direction associated with them. So it's better to think of them as little arrows. And by convention, the arrows always point to the right or upward. And that becomes important when we start thinking about calculating fluxes. So we can look at a third element of this grid and that is the cells. Before I do that though, let's go back to this initial block here. Remember that earlier I described a world where we would have cells that each contained a node, but that were also surrounded by a ring of boundary nodes. So taking off on that idea, can you guess how many cells are gonna be associated with this four by five grid of nodes? And if anybody wants to be brave and has to guess in the chat, go for it. All right, good for you, Rachel. Almost right, it turns out we only have six cells. It's only the nodes around the perimeter don't have cells associated with them, but all the interior ones do. Let's see, and Irina pointed out in the chat something I should have said earlier, which is to follow along, simply run each cell. And the way to run the cell is either to select it and click run or to hold down the shift key and press return or enter whatever it is. And yeah, it's important to run the cells in order because if you run them out of order, things can get out of nested. Okay, so what we'll try now is a little experiment. I've highlighted here three exercises for you to try on your own. We'll take, I think, just five minutes for this, let's say, until 50 minutes past the hour for you to try making your own raster model grid and querying some of the element ID numbers. And if you need any help, don't forget use the chat and I can also try to make breakout rooms for everyone as well. We'll continue on. Of course, not being in the same room together, it's hard to see how many of you have succeeded in creating a grid and finding out some of its elements, hopefully everybody, but I'll quickly go through the solutions here. So here we're gonna create a new raster model grid. So my cool, no, let's give it some shorter name. SG for some grid, raster model grid. Five by seven with spacing of 10 units. And I want to plot the layout as G. And I want to see the nodes. So I'll use the at keyword to say at node, there we go. So this reveals that the center most node is node 17. I want to find the cell that contains that node. So I need to see cells. So here I'll just copy this, paste it over here. Of course, copy and paste is always dangerous, but I think this will work. Change node to cell. And it tells us that it is node seven, or sorry, cell number seven that sits inside or sits around the center most node 17. And then something slightly trickier, we want to find the ID of the horizontal link that connects the last node on the right in the middle column. The last node on the right in the middle column, that's this one here, node 20. So once again, we'll use plot graph. Now I'll change it to cell, sorry, not cell, link. And it looks like it's link number 31, which runs from this one, which is 19 over to 20. Remember, they always point in the direction from left to right or bottom to top. Okay, great. If there are any questions, please go ahead and type them in the chat and we can take them as they come. Otherwise we'll move on. Okay, I just noticed we're missing an apostrophe list. Sorry, we can move it in. Oh, here, one small note. Notice that there's a couple of ways I can do this. I can use variables with keywords. So I could have said spacing equals, parenthesis five, seven, or I could just put five, seven. So these are, I've taken advantage of the fact that I know which argument goes in which place here. If you don't know that, then you can use keyword arguments. This also raises a thing that's useful to point out at some stage. And that is that you can always look up LandLab objects and functions in the online documentation. So let me go to that real quick. LandLab homepage, landlab.github.io. From there, if I go to reference manual and I go to index and click search, I can just do a command F here and say, for example, raster model grid. It looks like the rate one. And it will take me to a documentation page that will list for the initialization function the various arguments it takes. So we have shape, spacing, sorry, x, y spacing, x, y of lower left, x, y of reference and so on. And then Irina points out, I use different syntax for the x, y spacing in the early example, one of these, right. So here's an example of using the keyword argument with the keyword x, y spacing. So in fact, we could have left off the x, y spacing equals as long as this thing was the second argument. Either way works. Okay, so now that we've gotten our feet wet a little bit, making grids, let's go ahead and start creating our numerical model. So we'll make a new raster model grid, this time a little bit bigger. We'll make it 25 rows by 40 columns, 10 meter grid spacing and we'll call it mg for model grid. There's our object. Now the next thing I'm gonna wanna do is to add a field to the grid, an array of data that's gonna represent the land surface height at each grid node. And I wanna store that field in a variable called z. And to do that, what I can do is call a function that is attached to the grid called add zeros. So you may or may not be familiar with this kind of typical object-oriented syntax where I have the name of an object and the name of a function. So that's calling a function that is attached to or associated with this particular object. This function is called add zeros and what it does, it will create a new array tied to a particular kind of grid element and initially filled with all zeros. So to do that, I need to give it a name. So every field has a name as a string that you give it. Here I'm gonna call it topographic underscore underscore elevation and this app keyword tells add zeros that I wanna put this, these values associated with nodes. Okay, so there it is. So I now have my first field. Next thing I'll do just as a point of illustration is we're gonna look at a plot of the nodes in the grid. And this is a big enough grid that if we made a plot with the numbers of each node it would get pretty messy. I really just wanna see dots. So I'm gonna show you another way to do this. I'm gonna use a matplotlib function. We imported matplotlib earlier. We use the plot function to plot the X coordinate of each node against the Y coordinate of each node. And that information is available in two grid variables, one called X of node and the other called Y of node. And that's typical for land lab grid syntax. It'll be a property of element is the pattern. So here we're gonna make a plot of a bunch of grid node positions with a dot at the position of each grid. So you can think how many are we gonna see? There they are. So it was a 25 by 40 grid, 10 meter spacing. So our overall domain is 400 by 250. Now, math says we should have a thousand emotion values if you get a thousand grid nodes and we can test that by just looking at the length of this Z array and indeed it's a thousand. So one question that comes up is, can we assign multiple fields to a node? And the answer is yes. I can make as many fields as I like and I can assign them to nodes or links or cells or any other element in the grid. And it's common to do so. It's usually you need more than one field. Although for this example, we are only gonna need a couple. So okay, so what I'm gonna do next is we're gonna create a fault scar. We're gonna do some digital tectonics. I'm gonna modify the elevation field so that roughly half of the domain gets uplifted in an earthquake. And to make it interesting, we'll have the fault angling across the domain and we'll have a little more uplift on one side than the other. In the interest of time, I won't step through the details of doing that. You can read it on your own. It's not that complicated but the next couple of lines accomplished that. What I wanna do now is take a look at the result of my digital earthquake. So I will in plot import from landlabs plot in show library, a function called in show grid. So this is a sort of version of the matplotlib in show command that will give us an image that we can look down on that's colored according to whatever field we desire. And since we have one field so far, we're gonna use that field. The syntax is in show grid, the grid variable, the name of the thing or the array that we want to plot. So here it is. Okay, so here's our freshly ruptured false scar. It looks a little jaggedy because we're using a fairly coarse resolution grid. All these nodes have been uplifted by as much as I guess that's about 13 meters over here. And all these nodes down here have been left at zero elevation. So that's our starting land. Now we need to define some parameters. And for this problem, there's a couple of things we need. We need, first of all, our hill slope pre-coefficient, big D, which has dimensions of length squared per time or we'll use meter squared per year. And we'll assign that a value of 0.01 square meters per year. We also need a time step size. We need to decide how big are the time iterations we take. And some time step isn't arbitrary for the kind of solution method we're using. There is an upper limit beyond which the solution will go unstable and create sort of crazy looking and unrealistic patterns. Fortunately, numerical analysts have figured out for diffusion problems like this one, what is the upper limit? So here we're gonna do a little calculation. We happen to know that DT, our time step size, is gonna have to be smaller than one half times the grid cell spacing squared. So there's mg.dx, that's our cell spacing, 10 meters squared, divided by our transport coefficient. So we'll just calculate that here. And it turns out our time step will be 2,000 meters. All right, and just for fun, we'll decide that, you know, I'm happy to have soil flow off the north edge here if it wants to, and I'm happy to have soil flow out the south edge, but I don't want any soil to flow east or west. I don't want any soil to cross these sides because I'm imagining that this is sort of an infinitely long scarf that extends for a long direction on either side. Well, there's a way we can do that. And the way we do it is gonna be to take the nodes along the edge here and flag them as closed boundary nodes. You'll see in a moment how this is gonna work. We'll do the same for the nodes on the right. Well, there's a handy function to do this called set closed boundaries at grid edges. You see again, it's a grid function. So the syntax is name of grid dot name of function. And these true-false statements apply to, respectively, east, north, west, south. And that's typically the order in which things that go around the circle in land lab are arranged. East, north, west, south, E news. So we're gonna say, yes, the east is closed. No, the north is open. Yes, the west is closed. No, the south is open. So we'll get into a question about how do we differentiate between these nodes that are on the perimeter and are called boundary nodes and the ones that are on the inside? The terminology for nodes that are in the interior of a domain and are considered sort of part of the state space of a model are known as core nodes. And we can find out the ID numbers of the core nodes through a handy little array that you can access through the grid object called core nodes, so mg.core nodes. And if we wanna know how many core nodes there are, we can simply ask for the length of that array. Turns out there's 874 of them in this case. All right, we're almost ready to run our model. One more thing we'll do, and that is we wanna define our Q. In this particular notebook, I called it QS. S is for set them. So for that, we're gonna add a new field. We'll call it sediment flux. And we're gonna associate that field with links meaning we're gonna calculate the sediment flux at links because that represents the flux from one node to another. So here's the syntax. You see it's the same as before. We use the add zeros function. We give it a name. We tell it now that we want it to be at links. All right, so now we have two fields. And now the moment we've been waiting for, all of that is set up, now we get to a loop. What we're gonna do in this loop, and I'll walk you through before I run it, we're gonna loop over 25 time steps. Our time step is 2,000 years. We've got 50,000 years of landform evolution. And for each step in that, each iteration, we're gonna do four things. The first thing we do is to calculate the gradients. We're gonna calculate the slope of the land surface. And we're gonna calculate those slopes at the links. So we wanna know what is the slope between each pair of adjacent nodes. So there's a handy grid function already built to do that and it's called calc-ad-ac-ac-ac, the node field that you're interested in, in this case, z, or elevations. And it will spit back an array of slope values, gradient values, one per link. And we'll store that in a variable called g. So once we run that line, g is one per link value of the slope of the land surface. The next step we wanna do is to turn that slope into a soil flux. Remember our original equation was soil flux is minus d times gradient. So this is the numerical version of that. So the big picture here is we're taking minus d, multiplying it by the gradient, that produces a new array with one element for every link. And we're gonna have that array, the results stored in our array QS. Now notice that I'm not doing this for every link, but I'm only doing it for some. So what's this about? Mg.activelinks, what is an active link? An active link is one that connects either two core nodes or it connects a core node to an open boundary node. Well, okay, fine, that sounds like all of them, but let's think about what an active link is not. It is not a link that connects one boundary node to another. We don't really care what happens between two adjacent boundary nodes. And it's also not a link that connects a core node to a closed boundary node. What that means then is if I restrict my calculation of flux only to active links, I won't calculate any flux off the right and left sides of the model because all of those links are tagged as inactive, right? And that's how I can make walls or closed boundaries in a solution space. So all of those entries will just stay as zero. All right, once that line is run, I've calculated my fluxes now for the tedious part. Now I've gotta take each little square and I've gotta calculate all the inflows and outflows of soil during one or what the rate of inflow and outflow of soil is, right? I have to take the north face, south face, the east face, the north face and calculate all these things out. Well, there is a function that will do that for me and it's called help flux div at node. Flux div stands for divergence because that's mathematically what this operation is. I give it my flux located on links and it will send back to me the sum of all the volume inflows and outflows along the edges of each square divided by the area of that square. That is the rate of erosion or deposition, the rate of which the elevation is going up or going down as a result of net inflows and outflows of soil, right? So that array will have one number per grid node and we'll store that array in a variable called dz dt indicating that this is the rate of change of land surface height. Okay, finally, we're gonna update our elevations. We take our array of rates of change, we multiply it by one time step and that says how much elevation have I gained or lost during this one time step? And we will add that change to the current field of elevation values. Now here again, we're not doing it everywhere but we're only applying that operation to the core nodes. We want the boundary nodes to stay as they are. They're gonna stay unchanged. So for example, the nodes along the south boundary or the bottom boundary are gonna stay at zero no matter what. And that means that any soil that leaks into them is just gonna be vacuumed up. Let's say there's a little stream there that's washing it away, right? So that's an example of a fixed value boundary condition. All right, so again, four steps. Calculate gradients, calculate fluxes, calculate rates of change, update values. Let's run it. Okay, that took a lot longer to explain than it did to actually run it. Now let's look at the result. So think first, here was our starting train. We'll go back here. And you can think to yourself, well, what's this gonna look like after 50,000 years of soil creation and no more tectonics. Here's what the model predicts it will look like. You see that our initially sharp escarpment has gotten smoothed off over time. There's been erosion of soil from the upper edge and deposition down here in kind of a luvial fan like thing, except it's not water. Okay, so hopefully that gives you a little bit of how it's possible to create a relatively simple numerical model in 2D, the flan lab. Let's see, how are we doing on time? We're doing pretty well actually, running a little bit behind where I wanted to be. That's okay. No, actually we're running ahead of time. So how about let's take a few minutes to play with these exercises if you like. It's just a few of them where you can experiment with variations on this theme and ask any questions that come up. And let's see, gosh, maybe we'll try to reconvene let's say 220. Sound good? Okay, so why don't we go ahead and regroup again. I'm gonna share my screen again. So it was easy, I would ask for a show of hands how many people managed to get these exercises to work. Maybe instead I'll just go through some solutions so we can all sort of check. All right, step one, I'm asked a raster model grid called my grid with 16 rows, 25 columns, spacing of five meters. And we will make a plot of the grid points. And so I'll follow the same pattern we used before. We'll take X of node plotted against Y of node. And just for variation, let's see, let's do an interesting color on cyan. So then we'll do hit injector. Is that a controversial statement about just plain old red? Okay, there's our grid. And then I asked you to query the grid variables, number of nodes and number of core nodes. So that ends up being pretty easy. We just go to my grid, dot, dot, dot, dot, dot, dot, dot, dot, dot. Obviously we use a print statement here, print my grid, dot, number, that same thing, number of core nodes. And just for the fun of it, we'll add number of node rows. Okay, so we have 400 nodes and 322 of them are core nodes. Next says, let's add a new field to our grid called temperature and attach it to the nodes and have it be initially zeros. So we can do that with the add zeros function. So my grid, dot, add zeros. First argument is the main temperature. And then I say at equals node with one end. Okay, it seemed to work all right. Now we'll change the temperature for the nodes in the top half to be 10 degrees Celsius. So, and then we'll display it. So here's how we can do that. There's a little Python trickery. I wanna create, I can use as an index argument a Boolean array if it's the same size as the array itself. And it will then apply the operation only to the true elements. The Boolean array I wanna use in this case is my grid, Y of node greater than or equal to, let's see, how big did we make our grid again? It was 16, five years, so it's 80. So let's say greater than or equal to 40. And those nodes are gonna get set to 10 degrees. And we'll use in show grid to see if that worked. My grid, okay, that seems right. Our color bar says these guys down here are all zeros, these guys appear all 10 degrees. Awesome. Now use the grid function, closed boundaries at grid edges to assign closed boundaries to the right and left sides. So we'll do that. My grid dot set closed boundaries at grid edges. And remember the order is E news, east, north, west, south. So east, yes, I'd like that closed, north, no thank you. West, yes please, and south, no. Ah, the 40, good question. That's simply because I know that my grid was 16 rows high and the spacing was five meters. So it's an 80 meter tall grid. And I wanted only the upper 40 meters to be the upper half, so the upper half is 40 meters. That's where my 40 came in, good question. Okay, so that line will set our boundary conditions. Now we can check and we'll do that by using in show grid again, my grid. One fun little fact about in show grid, you can give it the name of the variable or you can give it the name as a string of the field. So I'll try doing it the other way this time, temperature. And here's what I'm gonna do. There's an optional argument we can do called color for closed, which will assign whatever color we tell it to closed boundary nodes. We'll make those cyan. Let's see what that looks like. Okay, so there's our plot and what this reveals is that the nodes along the left side and the nodes along the right side have now been flagged as closed boundary nodes. And now we're gonna create a new field of zeros called heat flux. This is partly to demonstrate that it's possible. I'll just call it HF for short. It is possible to do problems other than topography. So once again, I'll use add zeros, heat flux. This time at is gonna be link. Now we're asked to verify that the new field array has the correct number of items. So I can say print length of HF. And I'll also say print my grid number of links. They ought to be the same number. Great, so they are 759 links. Next question, use the count grad at link grid function to calculate the temperature gradients and all the links in the grid. Okay, well let's try that. We'll call it key grad, the temperature gradient. So I'm being a bad programmer here and using unhelpfully short names, but I'm patient. So my grid, count grad at link. And what I need to do is give it the field that I'm interested in that's a node field and that's the temperature. And now we're asked to think about, well, what should the maximum temperature gradient be? We know that some of the nodes have 10 degrees C and some of them are zero degrees C. We know that the spacing between grid nodes is five meters. So it seems like 10 degree difference over five meters would be two degrees C per meter. One way we can do that, check that is to print out values of our array and see if indeed the biggest temperature gradient values are that. If you print out of all our link temperature gradient values, most of them are zeros, of course, because temperature is flat over most of our terrain, but there's that interface between the area that's 10 C and the area that's zero C and the links along that interface have two degrees C per meter. So check, let's see. All right, I think to keep us to time, I'm gonna leave this last exercise for you to try on your own and then we'll simply, so the next section of the tutorial here talks a little bit about the numerical math behind this exercise. Since we've already gone over that at the beginning of the session, I won't go through it here. I'll simply point out that there are a few exercises to follow this where you make a teeny, weeny little grid and can inspect the values of gradient and test your intuition as far as the calculation of divergence in flux and out flux is concerned. All right, so then we'll move on to part three, which is gonna show us how to do the same thing that we did before, but this time using a hexagonal grid. And the lesson, spoiler alert, is that it turns out it's the same, basically the same pattern. We're just gonna make a hex grid instead of a raster grid. So here we go. So I will import from land lab hex model grid. Yeah, total D&D grid. And I will create an instance of a hex model grid. This will be 25 by 40. We'll use a rectangular layout. It could also be a hexagonal overall layout. And we'll give it a field and we'll plot the node locations. Well, I should have asked you to predict what the node locations we're gonna look at, look like. Here you can see the nodes are, they're in a regular arrangement, but they're staggered. Each row is a little bit offset from the row above and below. This operation does exactly the same thing we did before to create a digital earthquake and it plots our train. So there's our train. You see it looks a little bit different because now we're using a hex grid, but it's roughly similar. And then finally we come to our main loop. So first we'll make our field for sediment flux. And then here's our loop of 25 time steps. And then we will plot the resulting elevations. You can think about, well, what is this is gonna look like after 25 iterations times 2000 years of soil treatment. So there it is, a smoothed out version of the original. So lesson, you can change grids often without having to change much about the rest of your grid. Okay, here's a set of exercises you can try on your own. They're essentially a version of the same exercises that we skipped before where you make an itty bitty little grid this time itty bitty little hexagonal grid. We'll skip that one for now and you can explore that on your own. The next section I wanna do is to perform the same model calculation that we did before, but this time using a pre-built land lab component. So there happens to be a component called linear diffuser that implements a numerical solution to a 2D diffusion equation using basically the same approach that we just wrote out in the previous sections. Now I'm gonna show you how to use a land lab component. So the first thing we're gonna do is to import that component and we import it from landlab.components and the name is linear diffuser. So one of the things you can anticipate then is that land lab components just like land lab grids are implemented as data classes. And when you create a component, you're creating an instance of a component, an object. When you create a component, you need first to have a grid already defined, at least for those components that use grids. So we're gonna start out actually making a hex model grid. We're gonna create a field and this is an important thing. Components typically will have some input fields that they need in order to operate. And so it's your job as a programmer to make sure your grid already has those input fields at the time you create the component. In this case, this particular component needs a topographic elevation field. It has to be an array that is attached to nodes and that has this name topographic double underscore elevation. So we're gonna create that here and we'll initialize it with the same digital earthquake that we did before. So now we have our grid and we have our field. Now we're all set to create a component. And here's the syntax. It's pretty much the same syntax as with creating grids. One difference is that the first argument to a component is a grid that already exists. In this case, I called it MG. Arguments after that are usually arguments and parameters that are specific to that particular component. So this component needs one parameter and that is the diffusivity coefficient. Since we had already defined a variable called D that contains that value, we can simply pass D for that argument. All right, now that I've run this line, this variable LD represents a data object that is our component. It has all the data that the component needs in order to run and all the functions it needs to run. So now we're ready actually to have a little loop and run our component iteratively. And you can see in this next line that our loop is pretty simple. The component has this one function and I should stop here and clarify one piece of syntax. You'll sometimes hear people refer to functions that belong to objects as methods. That's a standard object-oriented programming syntax. So if I use the word method, that's what I mean. I think it's clearer if we say function because that's really what it is. A method is a function that's bound for a particular class. So it has a function called run one step which takes one argument and that is the duration of the time step that you'd like to run. So this is exactly the same as the loop we looked at before except now this component's run one step function or method is taking care of all those operations, calculating the gradients, calculating the fluxes, calculating the net inflows and outflows and updating the elevations. The test set out, we can run it and there it is. Once again, we have the identical landscape this time calculated using our component. So it gives you at least a little bit of a feel for how to operate land map components. Let's see, what are we on? We're on section four, perfect. So how about this? We'll take just five minutes to experiment with some of these exercises. As we go along, we're getting a little more complicated here. So get as far as you get if you like and we'll pick up again, let's say, 246. We're almost there. There's one more collection of exercises in this notebook and then I thought we'd just take a few minutes taking a quick look at a very different kind of model, a model of overland flow. And hopefully one of the things we'll see there is how to import a DEM or at least one way to do it. So let's see, first of all, taking a quick look at some of these exercises. These are sort of long, so I think I'm not gonna get to solving all of them here but maybe we'll take a stab at the first one or two. The first one is repeat the steps above and instantiate and run a linear diffuser component but this time give it a raster model grid. Okay, well, that ought to be pretty easy. I can pick up a code here. Let's get nervous doing cop and paste because that's usually where those come from but since we maybe have a lot of eyes on this, hopefully you guys will catch me if I make any mistakes along the way. So I'm gonna change this from hex to raster model grid and I'll change the name to RNG or raster model grid. Change it there, there, there, there. So instantiate our linear diffuser and we'll pass it RNG and then we need to run our loop. So now it's the same thing but with the raster model grid, I think I changed all the NGs to RNGs. Let's see if it gives us a raster. Oh, error. Ah, right. Raster grids don't take no layout as an argument. That way, try again. All right, no, not all right. Ah, I forgot to change this one to an RNG. Let's try that again. There we go. Okay, let's see. The next one is an exercise in running it with uplift. I'll simply give you a hint on this. Here's our main loop. We need to do this would be to stick in a line here that takes all the core nodes and adds a little bit of extra elevation during each iteration. And that extra increment of elevation would be the uplift rate that you want times the duration of one time step. So it should look something like Z, RNG.core nodes. I only want to uplift the core nodes. Otherwise I won't have any relative uplift. Just the whole thing will be rising with no change in the boundaries. Plus equals, and that's where I would put in uplift rate times DT. I've almost given you the answer there but I haven't demonstrated that it works. So maybe it works, maybe it doesn't. Okay, so I'm gonna leave behind this notebook next. So if there are any questions, ask them now. I see Shanti asked about getting a copy of this notebook with the answers all worked out and I think we have that already. So that's easy to do. The other burning questions before we jump to a different notebook. Okay, so switching over to the other tutorial in this series. I'm actually just, for the element of surprise, I'm gonna clear all the cells to start. So this is one of a couple of tutorial notebooks that walk through a component that's called Overlying Flow. So this is a component that was written by Jordan Adams when she was a PhD student at Tulane University. She's now an educator in Louisiana at a community college. It's been publishing papers on this Overland Flow model and some of its implications for various questions in geomorphology and erosion. This particular component is a solution to a form of what's called the shallow water equations that uses a modified diffusion wave formulation. Maybe you're familiar with this sort of line of inquiry and it has a numerical momentum term. It's the same algorithm that is used by people who do flood modeling. It was developed by D'Alameda and colleagues in Paul Bates' group in Bristol. So thanks to Jordan for putting this notebook together. And here I'm just gonna run through it. We don't have any exercises built into this one but I'd like to give you a little bit of a flavor for you. Okay, so we'll start off with some imports and I'll simply point out that the imports include two functions that we haven't seen before. Read Esri Asti and write Esri Asti. Both imported from landlab.io, I-O for input output. So these are functions that can read, write, gridded data, gridded raster data in the format that RGIS uses as it's. We're gonna import a component called, actually I'm sorry, am I looking at the right component? No, I'm not, forgive me. Let's go back. That looked a little too sophisticated. Here's the one I want. Okay, that's better. Okay, once again, I will clear output. Right, okay. All right, so from landlab components overland flow. We're gonna import overland flow, the component. Here's our read Esri Asti. We've got some matlab stuff or a matplotlib stuff we'll import raster model grid and a few other things. Next block, we're gonna set up some parameters. So what we're gonna do is we're gonna put a layer, we're gonna take a digital elevation model and put a layer of water on it as if we just had a very short, very intense rainstorm. And some of the parameters we'll need include the runtime. In this case, we're gonna run for a very short time just a hundred seconds. Here's the initial depth of water on the lands. We're gonna pour 10 centimeters on the landscape instantly. So that would be a pretty heavy rain. We need a roughness factor. This is Manning's M, so-called. We need G. We need a numerical factor that this particular component uses. Another velocity constant that the component uses and we'll see what these run slices are in a moment. All right, so that's a little bit of setup. We'll set, define a variable called elapsed time to keep track of what time it is. We'll start at time equals one second. And next thing we need to do is to read in our initial topography. So for this exercise, Jordan has created an artificially square drainage network topography. We'll see what it looks like in a second. Here's the syntax for the read as we ask you function. You give it the name of the file and a name for the field that will be created in the grid. And what that function returns is two things. It creates and returns a grid of the correct size. It's a raster grid. And it also returns a reference to the grid's field that you just read in, in this case, called topographic elevation. So that's Z again. Then we're gonna set closed boundaries at grid edges. We're actually gonna close off all the boundaries initially. So our watershed is surrounded by walls. All right, now at this point, we have our grid and we have our topography field that we've read in. This is sort of a subtle point that maybe worth making at this point. This variable Z, it is a reference to this block of memory that contains a certain number of values, each representing the elevation of a particular grid point. I can access that exact same block of memory in a different way by going to the grid object, looking at the at node attribute and in square brackets, putting in the name of the field. Those two things are two ways to refer to the exact same block of memory in the computer. And we can verify that by checking that this is, that the corresponding elements of these two things are equal for all elements. Okay, next thing we're gonna do is a little bit of boundaries set up. We happen to know that node number 100 is the outlet of the watershed. So we're gonna turn that into an open boundary. And the term for that is rng.bc for boundary conditions. Node is fixed value, meaning we're gonna keep the elevation at that point fixed. It's not gonna change and none of the other fields is gonna change either. All right, that's our watershed outlet. Now we'll create a field for water depth and we will add to the water depth, the initial water depth that just landed from that very short storm. All right, so surface water depth should now be 10 centimeters everywhere. Let's take a look at our terrain. Okay, it's admittedly a booking square drainage basin. It was actually created with another land lab component. What it'll do for our purposes. Now we're gonna create an overland flow component, an instance of the overland flow component. And as with any component, the first argument is the grid, rng. Second argument in this case is a numerical one that this particular component uses. If you have relatively steep slopes, you set this flag to tree. All right, now we have our component. And now we get to our time loop, we are ready to roll. In this case, this particular component doesn't use time steps of fixed duration. Instead, it has a way to calculate its own time step based on stability criteria. And that time step might vary through time, depending on the water depths that you have in the simulation and the water velocities. So our loop looks a little bit different. We are going to have a while loop instead of a for loop. And we're gonna iterate until we have used up all of our time, that is until elapsed time has reached run time. So here are the steps in our loop. The first thing we do is we ask the overland flow component to please tell us what kind of time step it would prefer to take on this next iteration. And it will return that and we'll store the return value in this variable dt. The next thing we do is we're gonna calculate flow for one time step. So land lab components will always have some kind of go method that does their thing. Usually if they're forward in time advancing models, it will need move forward by one time step. A terminology that we often use in many components is run one step, which you saw in the last example. For this particular component, the go method is called overland flow. It will simply take an amount, go one time step of whatever time step it has decided is appropriate. We'll print out the elapsed time and then we'll update the elapsed time by one step of whatever duration the model took during that step. So let's do it. Let's run that loop for a hundred seconds. All right, there we are. By printing out the elapsed time, you can see that the time steps that the model has taken are actually a bit different. The first one goes from one second to 22 seconds. So a time step of 21 seconds. By the time we get toward the end of the simulation, we're taking time steps shorter than 10 seconds. So it's dynamically adapted. Let's look at the water depth field. All right, pretty cool. You can see that water has collected in the various little rills and gullies and ravines in this very square looking landscape. Now what we might wanna do actually is have visualized this at multiple different times. So in this last block of code, we're gonna rerun that same loop, but we're gonna nest it inside a larger loop and pause every once in a while to plot the output so we can see the water depth field evolve through time. We'll look at three different time slices. And as we do, you can sort of try to predict how is this picture gonna change over time? So our time slices, we go back a second here where we define the time slices, we'll go 10, 50, 100. So we're gonna start from here and run for another 10, 50, and 100 seconds. So ask yourself how will this depth pattern change over time? Okay, so here's the output. The first image doesn't look all that different. The next one, you notice that some of the finer little grills and things here have been drained of water and the last image even more have been drained. So you're seeing this wave of water basically wash off the last one. Last thing I wanted to show, and hopefully this will be helpful to some, is to repeat some of that, but this time reading in an actual real terrain. And so for that, I'm gonna go back and comment out these two lines that read in the square terrain and instead we'll read in a real drainage base. Now this particular drainage base is in a square DEM with an irregular watershed inside it and all the cells outside the watershed have been assigned elevation values of minus 9999. So many of you will recognize that as the arc info code for no data. So we have a line here that will set all the no data nodes to being closed boundaries, meaning they're outside of the watershed and we don't want to deal with them. Other than that, everything is the same. So I'll go back and simply rerun the whole notebook and we'll see what we've got. Here's a map of our watershed. It doesn't look like much at this point because our elevation range is going from minus 9999 somewhere deeper than here all the way up to whatever the elevation is. So we can change this plotting command to have the minimum color be 1650 meters. This happens to be an example in New Mexico. So now we can see that there is some terrain there. And here's the run of our overland flow model and you can see water is washing down through the main branches of the drainage network. And when we run it further in time, you can see that some of these little tributaries here are starting to drain off so that by the last time step there's significant depth of water only in these sort of major branches of the network. Okay, so that's a really quick tour of a different kind of component, the hydrological component and hopefully that's helpful. So I think I'll stop there. Welcome any more questions but I'll hand it over to our master of ceremonies. Hey, thanks Greg. So I'm still a little new to land lab myself so it was really cool to see just what land lab can do. So thanks for that presentation. All right, so today we learned about land lab which is great for writing a new model. Tomorrow we're gonna look at two other CSDMS tools, BMI and PMT. And if you're wondering what those acronyms mean, tune in tomorrow. All right, so these two tools are useful for running a couple of models that may be written in another language other than Python, maybe C or C++ or Fortran for example. All right, so Eric and I will present more on that tomorrow. Okay, so that's it for today. So if you have any questions, we'll be happy to stick around to the chat and talk and answer questions. Otherwise, thank you very much Greg for your presentation today and we'll see you all tomorrow. Same time, same place.