 Okay, hi everyone and welcome. This is the second day of the CSDMS clinic on using the CSDMS tools, how to build a couple of models. So yesterday we covered land lab and today we're gonna cover BMI and PIMT and you get to learn about what these mysterious acronyms mean. So I'd like to start by sharing my screen, right? And you can see I've started at the repository. So again, this is the repository that we're using with all the notebooks in it. And we use this yesterday as well. And so I want to use this to quickly go through the schedule for today. So I'll scroll down. There's part one yesterday. Part two. So we'll start with a brief introduction to PIMT. Eric wants to kind of set the stage for the next part, which is BMI, and then we'll circle back and talk about PIMT again. As you'll see as we go through presentations today, these two pieces of software are very closely related. Okay, so with that, again for logistics, if you have any questions while we're doing our presentation, please type questions in the chat. And as Arena mentioned, we probably have a small enough group that maybe we could try also just, if you wanted to, unmute your mic and ask a question live over the air. I think that could be kind of fun. I know, I love this, I know Arena loves this, like the classroom experience of being able to interact with people. So if you feel motivated to ask a question live, go for it. I think it'll be fun. It'll probably add to the workshop environment as well. So with that, I think that's all that I have to start with. I'd like to turn it over to Eric, who will show a little bit about PIMT. Is that cool, Eric? That sounds great. And yeah, if people wanna just jump in and ask questions, that's also fine too. All right. All right, I'm gonna try to share my screen here. Is my screen sharing? Yes. Says it is. Yeah. So I guess maybe the best place to begin is where we did yesterday at the GitHub repo for the workshop. So it's github.com slash CSTMS CSTMS 2020. And then we'll again clone the notebooks that we added so that we can go through the tutorials. So if everyone's there, you can click on, confusingly, the second link here. So get the workshop tutorials for part two, PIMT. So that'll download all the PIMT ones. And then when Mark does his, we'll come back here and you can click on the VMI ones and I'll take you to those. And if someone doesn't have an account yet or can't get on, we can take care of that. But if everyone is here, I'm gonna take you through a quick introduction to PIMT and maybe some motivation for why we wrote PIMT. And a quick example, and then I'll hand it back to Mark to explain some of the technology behind VMI and what makes it work. So first, PIMT, so I'll start with a story. It begins a long time ago when CSTMS was first beginning or when before CSTMS began. And there are some meetings to figure out what something like CSTMS would look like and what it wouldn't look like. And so this was in the early 2000s, but I was looking at some pictures recently and judging by how young Greg looked, you would have thought it was early 1980s, but it was only 20 years ago, Greg. One thing that they decided they didn't want was one large model. So there wasn't this one giant model that everyone would contribute to. They wanted lots of small models. So everyone would contribute small toy models perhaps. And then those models would communicate together with one another. So that was a lot different than what, say, the atmospheric community was doing. So we had to come up with something different and this is the reason for PIMT. It tries to solve some of these problems. So I've outlined some of the problems here. This notebook is mostly just text, but outlines some of the problems and the solutions that PIMT tries to implement. So the first problem that we had at the time, this isn't as much of a problem anymore with source code. We needed to get source code from the developers to the community and this used to be a problem, believe it or not. There wasn't GitHub. And so people would often keep source code hidden for whatever reason in their drawers and it was hard to get. So you had to communicate with a professor or a scientist to get that code and you had to ask them for it. And if they were not too busy and nice, they would email you a copy of it. And then you would go and work in that source code and there'd be, you would add something to it and then it would branch and there'd be different versions of models. And so we don't want that. So as part of CSDMS, we've, all the models in PIMT are open source. We ensure that so that you can get the code easily without going through a gatekeeper. And you can run it through PIMT. So all the models in PIMT are available, not always on GitHub, mostly on GitHub, but in a public repository. Another problem with all of these different models is that they come from different languages, because we don't have one giant model. We don't agree on a single language to commit the code to. And so there's a compiling issue. So all these models are written in different languages by different scientists. This can be the most difficult part of building models and running models. Oftentimes they're written in Fortran or C, which are compiled codes. And you may need some special libraries to install with them or some special secret magical compile flags just to get them to work. So that's a pretty big problem. Oftentimes people just want to, they don't even really want to look at the code. They just want to try to try to run it. And yet they encounter all these problems with compiling it. They want to solve this problem. And so in PMT, as part of PMT, we build all of the models that are part of PMT. And so we offer binaries of them, pre-compiled binaries on as many platforms as we can. Generally Mac is the easiest and then Linux. And then we'll concentrate on Windows after that. Now, we solved this problem primarily through Konda. I think many of you are familiar with Anaconda, which is a Python package or a collection of Python packages. So it's a distribution. And but one of the things with Anaconda is the package manager that you use called Konda. And Konda is a great thing. It packages up source code into packages that can be just easily distributed, compiled and distributed. Now Anaconda is primarily Python, but Konda can install programs in any language. And so we have recipes for each of the models. And then we build these recipes on several different platforms and make them available through a platform called Konda Forge, which is a community repository for Konda recipes. So as part of that, the recipe also gives users a very specific way or shows them a very specific way to build the models. So if they wanted to compile it themselves, they could. And this is regularly tested. And so this is something that we provide. So it makes it very easy for new users to install models, as well as more advanced users to maybe tinker with the source code and then recompile it for themselves. A lot of the models don't come with a lot of documentation. Documentation is sort of secondary for a lot of people when they're writing models. But models that are contributed to PMT can tie into the PMT and the LandLab documentation system to make it easier to show your users how to run your model and where to get information about it. So the documentation comes with PMT, as well as some of the testing and the continuous integration that we do with PMT. So every time someone commits a change to PMT or one of the models, tests will be triggered and run in several different platforms. And so because they're tied into PMT, all that machinery is in place. And so you can take advantage of that. And running the model. So you've compiled it, you've installed it, and now you need to run it. Well, sometimes that can be quite confusing, especially when we have these models coming from all different authors and different sources. Many of them have very idiosyncratic designs. Their input files are, they're not standard formats, let's say. And so they can be hard to get running. And so PMT tries to solve that too by making all models and all components look very similar. And so that if you know how to run one model, you'll know how to run them all. At least to get them started. Debugging, so PMT, because it is in Python, it's an interpreted language. And it makes it easy to debug models or just to run and play with models, maybe not necessarily debugging them. But we bring models that are written in C or C++ or Fortran or Python, we're adding new languages. We're working on adding other languages as well. But we bring those models written in those languages into a Python framework. And so now you can interactively run the model. So you could step through a model run. So this is a model that was written in C that you would not be able to do this with before. You could step through in time with the model and then change some inputs maybe, see how that affects some outputs. If you were doing some debugging, you could look at some of the outputs and see when they turn to NANDs or negative numbers or whatever. And you can plot up the results as you're stepping through the model. And this is all because we can bring them into a Python framework. So that is off, that's very useful as well. And then model coupling. And so this is the last one. So PyMT brings a lot of things to your models and then also is model coupling. And so when we're coupling different models, there's some problems that we encounter, like the two different models are written on, written to use different grids. So one model has a structured grid, one has an unstructured mesh, and PyMT provides some mapping capabilities. So we use the Earth System Modeling Framework, grid mapping tool. So for instance, you could run one model on a rectilinear grid for one time step, map it on to an unstructured mesh, if that's what the other model is running on. And then run that model for a few time saps and then go back and forth. And so we offer that as part of the PyMT package. There's other utilities like unit conversion. So your two models may use different units and we can easily accommodate that. Time interpolation. Perhaps your two models are running and they have different time steps and they can't synchronize themselves. They just aren't at the same time step. You could run the time interpolator would then run one model, say a little bit past where it needs to go and then save the previous time step and interpolate between those two times to get the value that the other model needs at the correct time. So that's another utility that we offer. So with that, that's sort of the motivation for PyMT and why we wrote it. So now I'll take you through some of the models or I'll take you through a model and just so you can see what a sample model looks like in PyMT. So now you can run, now we'll run some code. So the first thing that you do with PyMT is you import PyMT.models. So PyMT.models is a is a sub package or a module of PyMT that contains all of the models that we have now here. So we have some models preloaded on our Jupyter Hub and this is the ones that we have here. So I'm gonna go through several or I'm just gonna go through one model just to show you what it looks like. So I'm feeling a little bit brave so you guys are able to pick whichever model you want. I'm choosing plume but the code should work pretty much the same. There's gonna be a couple of changes for if you choose another model but regardless you should be able to run through this brief example choosing any of these models because the interface is the same for all of them. So I'm gonna choose a plume model. This is a model that models a buoyant plume entering the ocean, sediment plume. So the first thing we do, so I'm just gonna have this plume so plume with a capital P is a class and I'm just gonna assign it to the name model just so I don't have to type plume every time. Especially if I change the model that I'm importing. So yours would look the same. Then we instantiate it like we do with all classes in Python. Okay, so now we have a model that we can run and we can do a lot of things to it. So you can get just like any object in Python, you can get some help on it. So here, so if you've chosen a model other than plume you should get a little bit different message. So here this model gives a brief description or the help message gives a brief description and the author and some links and the license. So we wanna make sure that people get credit for their models that they contribute to PMT so that it doesn't become just part of PMT and we don't take credit for it ourselves. So we wanna make sure that the person who wrote the model is credited. And then if you scroll down it's a typical doc string for Python. It lists all the parameters that you can pass as keywords, these are all optional. For the plume model, yours will look different if you've chosen a different model. And all these parameters are values that you can set for the model before the model starts running. And we'll get to that in just a little bit but we'll probably come back to the help. It helps awfully useful. You can also get these values programmatically. So with the parameters attribute of model that returns a dictionary of parameter name and the default values. So we list them out here. So this is again, yours will be different but this is, so the river mouth velocity for this plume the default is one meter per second. So all models will look like this and then we will go to the life cycle of a model. And so this is how you advance the model in time and how you actually can run the model in different steps. So we'll go through this a lot today probably but I'll just do a brief introduction to this. So the first thing your model needs to do is set up. So this is where it prepares all the input files for the model. The next step was initializing. So it's gonna read in all the input files, maybe allocate some memory and get ready to do some time stepping. The third step is updating. So this is where we advance in time at one time step at a time and then the finalize is when it's just stuff that's written at the end. So it may be freeing up memory, writing some output files, that sort of thing. So the setup method. So every model component has a method called setup. So again, a method is just a function that's attached to an instance of a class. So you can call the setup method as I've done here within the first line. So the first argument to set up is the name of the directory where you want all the input files to go. So in this case, I'm just gonna call it my model will be the name of the folder that I'm gonna create and put all the input files in. And if you left that out, PyMT would have created a temporary directory in some location, probably starting with the input file. And so because we did this, my internet is unstable, it'll just be the defaults. So I'm gonna show you some of the, just to give you an idea of what's going on here, I will show you the output or the input files. So if you were to open up a terminal, you don't need to do this. I'm just gonna take you through it. So here's a folder called my model. And so it's created three input files for me. And if you wanted to look at one of the files, so here's just an input file for the plume model. And so this is an input file that's specific to plume. And so, but you don't need to worry about this because you're using PyMT. This is just to show you what PyMT has done. So it's created a nice set of input files. So if we go back now to our notebook, as I said, those are just default values, but you could, if you wanted to change some of the default values to non-default. So here I'm gonna create another folder with the river mallet velocity being pretty fast. So this is two meters per second. The keywords that you would pass to set up are those that we showed earlier if we scroll up a little bit. So you can see them in that help message. So I think these are all alphabetical order. So here's river mallet velocity. So that's how I knew that that was a valid keyword argument that I could pass to it. The default is one meter per second. So I will create a new one with a velocity of two meters per second. And then if you were to go back here, hopefully, so now the velocity is two meters per second. So the setup method gives you a nice way to edit model input files in a very standardized way. So to the user using PyMT, they don't have to actually worry about all the idiosyncratic input files that different models have. Everything is done programmatically through keywords. The setup method also allows you to set up multiple input files. So a common use case that I've seen is that people want to do some sensitivity tests on their model. So maybe here you'd want to run plume and see what it looks like for a range of velocities and a range of widths. So what you could do is type some code like I have here and maybe I'll, yeah, I think we've got time. So we can do this, so we can paste this code into a cell. So we're going to set up some velocities, some sample velocities and widths. So we're going to have widths that range from 100 to 500 meters and velocities range from a half a meter per second to two and a half meters per second. And then all we're going to do is just run the set method right now and then we'll create a bunch of input files. And then what people would not usually do is then have maybe run all this on a cluster so you could run all these independently of one another for a sensitivity test there. So I just created a number of input files all with varying widths and velocities. So the product function here, what it's going to do is take every combination of the velocities and widths. So we have five different velocity samples and five different width samples. So we're going to get, we should get 25 different input files or input file folders. So if we go back to the terminal, which I will go back here. There we go. To the terminal. So now we have, see all these same directories now. So we have 25 simulation directories each prepared with a different set of velocities and widths. So now you could take this, put it on a cluster, run each simulation on a separate node in parallel. Good use of a Jupyter Hub terminal, Eric. What's that? Good use of a Jupyter Hub terminal. Oh yeah, thanks. Thanks, Matt. I love the terminal. I love the terminal. Yeah, all right. So then we do this. So now we've said that we've created a, a default, so I'm going to go back and just create this input file here. Because I may have screwed things up when I'm typing. So now, so we're going to create a default set of input files. And now we call the BMI method initialize. And so the first argument is the file name, and then the second argument is where that file is and where the component should be initialized. I'm not sure why it's taking so long. Oh, so I think I screwed up with the, when I was creating all those input files. So this will probably happen to you when you're running these notebooks and that's totally fine. So if the kernel dies, you can go up here and go to restart. So what probably happened is that I messed up and created an input file with some bad inputs. And so what is going to happen is that because the underlying model in this case was written in C, the model itself doesn't have the really fancy memory management that Python does. And so sometimes, if depending on the person wrote that model, it may just crash unceremoniously and stop working. And that's generally the problem that you see is when the kernel dies. So to clean things up, I'm going to start from the beginning and then I'll just skip all the way down to the initialize step. And hopefully I've set that up, right? Or maybe I created a new, there we go. So now the model is set up and ready to go. So the memory's been allocated but it hasn't run any time steps yet. So for your model, if you're using a different model, then you're going to get different things because we're looking at it right now is all the input parameters and output parameters. So these are different than what we passed to the setup method. The setup method were parameters that are specified in an input file. And so they don't change through time. They're just initial parameters. Now these input var names and output var names are the names of the variables that can be set while the model's running. So for the plume case, we can set, we can vary the water speed with time. So the river velocity, the river depth and then the output, there's only one output variable which is the deposition rate. So if we do, so actually why don't we just write the name here. So the output variable, so we're going to get, so we can get information about the output variable in this case of C bottom sediment deposition rate. So it's a variable that has units of meters per day. It's on grid one. And so Mark's going to get to this stuff. This is a BMI thing. So a model can have multiple different grids and those variables can be defined on multiple grids. This particular variable is defined on grid one and we can get more information about grid one later. But for now, we're just going to skip over that. And then it's defined on nodes. So grid can have different elements like Greg showed in land lab yesterday. In this one, it's defined on nodes. And we can get its actual value. So we can do, so the variable will have data or you can also get the same values with the same array with a get value function. That's another BMI method that Mark will talk about. And you can run the model. And so now I've updated it for one timestamp. So we're one day in and then we should be able to verify that by these other BMI methods which is get start time, time and end time. So now we know how to set up a model simulation, initialize it, update it with time. And then the last thing we would do is possibly run finalize, which many times it doesn't do anything but can free up memory to the system so that you can, so you don't run on memory. And then it could write output files or maybe print some plots or something. So that's quickly what a model would look like. And hopefully they work for you and the interface for all of those models that we showed at the beginning, I'm gonna scroll up. So Evolvge and Plume, Sedge, Lux, Subside, all of those methods that we just ran through should work for those. You still have to know how to use the model to some degree, but you should know at least the logistics of setting it up and running it. And the basis for all of this is the basic model interface, which Mark hopefully is ready to talk about. But if there's any questions, maybe we should just take a break and that was pretty fast. So if people have any questions, we can answer those now or Mark could move on to BMI. Well, why don't we take just a minute and see if we have any questions. Maybe you could unshare and I can share. Yeah, sure. Oh, I see a question from Saurav. Do you wanna take that error? The one, why are not all BMI compliant models on the Pi and T? So the ones that we have loaded up for on our Jupyter Hub right now are just the ones that are just a sub-sample of them so that we have some notebooks for them and we're pretty sure that they're working well and are useful. But yeah, so that's why we don't have them all right now. Some of them could be 3D. So set flex 3D produces three-dimensional stratigraphy. It's a 2D model, but it generates three-dimensional stratigraphy. So the grids, generally we use grids like land lab where they're plan view and they have an X and a Y. But with BMI you could also specify 3D grids. Although we don't, I'm not sure that we really have too many real 3D models. Okay, I'm gonna share my screen and start. If you guys have more questions for Eric and us just ask them in the chat or again, if you feel bold, unmute your mic and ask over the air. Okay, so I'm sharing my screen and I think what I'll do is start with some slides. There's no nice keyword shortcut for this. Here we go. All right, can you guys see my slide? Is that there? Yeah, you got it. Okay, all right. Okay, thanks. All right, so Eric has set me up nicely by showing pi MT, which shows an immediate application of BMI. All right, so I like to talk about BMI, the basic model interface. I have a series of slides. I like simple slides. So the first slide is what? What is BMI? And it's simplest, BMI is just a set of functions. These functions are used to run the model. Now, there's obviously, there's a lot more detail, there's more specialization, but at a high level, it's just a set of functions that you can use to run the model. And the key here is that we're standardized. The fact that the basic model interface looks the same for any model written in any language. That's the key thing in the real strength of BMI as well. Why? Why would I want to use BMI? Why is it important? What utility does BMI have? Here I'm going to use an analogy. This is one that Greg came up with and used at the CSDMS annual meeting last year. And I liked it a lot, it really resonated with me. Not necessarily because I'm a car guy or anything, but more because basically everyone that we touch has some experience to the car. You kind of know what a car is and how it works. So why, what's this car doing on my screen? All right, so think of a car. When I get into a car, I have an ignition so there's a way to start the car. I have controls like a steering wheel, like a brake pedal, like an accelerator, so I can run the car. I have feedbacks, I have a speedometer so I can find out how fast I'm going. I can also set things like I can turn wipers on, for example. All right, so the nice thing is that with a car, we kind of have this standardized interface. So my wife has a 2013 Subaru Outback, as you see pictured here. So I know how to drive that. Now the neat thing about the fact that this is a standardized interface is that, say for example, I go to Home Depot and I want to run a truck because I want to move a sofa, which I've done recently. The neat thing is, is that without ever having seen that truck and eventually we got a van, interestingly, I know, I'm almost positive that I'll be able to drive that truck and or van because it has a standardized interface. The truck has a steering wheel, it has an ignition, it has a brake pedal, it has a gas pedal. All right, so the fact that the interface is standardized means I'll know automatically how to drive it. And that's again the idea behind BMI. BMI provides a standardized interface for driving models. Where? So where can I get more information about BMI? All right, for this I'd like to go to the documentation. So this, you know, readthedocs.io, the popular site for software documentation. And we've got the BMI name there. So let's go to web browser. And so this is the documentation for BMI. And I think there's a lot here. Eric and I and Greg had fun rewriting this earlier this year. There's two things I want to point out. The first thing is that BMI is an interface. And so for those of you familiar with software engineering, no, an interface is abstract. You need to actually implement it in order to make it work. And so we have language specifications and examples in four languages, C, C++, Fortran, and Python. We'll actually use the Python implementation today. And actually we're even gonna use the Python example, BMI example Python, which you see over here. We're gonna dog food it today. That's another software engineering term. All right, so that's the first thing. So recognize that there are specifications you can look at and examples you can look at for those languages, for these four languages. All right, the second thing is just to show you very broadly what functions are in the basic model interface. And you can see I have a table here where you can see some familiar names. Eric mentioned initialize and update and finalize. And you can see there are a bunch of other functions as well. And as Greg mentioned yesterday, there's about 30 of them or so. Actually, I have not counted them myself. I should really do that. Anyway, the idea is that when you add a BMI to a model, you would basically write these functions. And then inside the functions, you'd have calls into your model. All right, so this is the BMI documentation. Let me go back to my slides. We're hiding here. All right, so my next question is, how? How do we use BMI? And that's what we're gonna do next. So let's go back to our GitHub repo page. And I'm gonna be careful to click the link. The nice thing about the, so I'm scrolling down to find the link where we can get the BMI notebooks. And I'm being careful to click this link because I know that I've made changes and Eric has made some changes overnight. So by clicking this, I'll make sure that I have the most recent version. So let me do that. There we go, okay. So this is our little landing page for the BMI examples that I'll use today. I have two notebooks. I think I can get through them pretty quickly. I'm trying to think how much, how much time do you think I should take, Eric? Oh, whatever you need, Mark. Whatever I need, okay. Be careful when you say that. You know what I like to call them. Okay, I'll try to do this in maybe 20 minutes, maybe 10 minutes for each notebook, okay? You find one. I'll get a little after two. All right, so I like to look at the first notebook. It's called run the heat model. I'll open that up. There's the fun BMI logo. We had fun with fonts last year. So we have nice fonts for our logos. Okay, again, recognize, I like to switch the foreground and background colors in my browser to make it a little easier to see. So when you guys click this, it'll have a white background. All right, and a question as well. Is my font size large enough? Is this easy for people to see? Okay. Let us know in the chat window or feel free to unmute your mic and let me know. Okay, so in this notebook, I want to run a model called heat. It's a really simple model. It's just a model of two-dimensional diffusion of temperature across a uniform rectangular plate. It uses deershly boundary conditions. So basically they're clamped boundary conditions. It's kind of funny, I didn't realize this, but Greg did the land lab examples yesterday and the land lab linear diffuser component, I think is very similar to this actually. So we're looking at diffusion again. Now, I know that it's not the most interesting scientific problem to think about diffusion, but the nice thing is that as scientists, we're all kind of familiar with diffusion anyway. This is something that's kind of, we learn in classes as we go through school. So it's a concept that's pretty familiar for everyone. So you can think about diffusion of temperature across the plate. Okay, and again, you can see I'm gonna use the BMI example Python repository. You can actually look at the source code. Let me take just a moment to show that. So you can see in this first paragraph, I have a link to the source code. I'm gonna open that up and take just a couple of minutes to talk about it. Okay, so this isn't Python. Again, we like Python and CSDMS. It's an easy language to use and a lot of people are familiar with it. In this file, you can see that there is a function, the solver. All right, so this is the solver that solves the diffusion equation. There's then a class called heat and this class can be used basically to set up the temperature distribution across the plate as well as give things like the dimensions of the plate. So the number of rows and number of columns, the spacing between the grid cells. It's gonna be, by the way, a uniform rectilinear grid, kind of like what Greg used yesterday in the first examples in LandLab. It also sets up the diffusivity. I'm trying to think if there's anything else super interesting about this. I think that's about it. Oh, here we go. This is one thing that's interesting. So note that this, there we go. Note that this heat class has a method and remember Greg mentioned yesterday, method is just a function attached to an object. So it has a method called advance in time. So this is what we use to step through time in this model. All right, so this is the heat model. Super simple. Again, we can have many different ways of doing this but this is the one I'll use. This is the one we use across all of our BMI examples. Okay, let's now, as the title says, run the heat model. So I'm gonna, oh, actually I was gonna say, I'm gonna step through. You can see I've already saved the notebook with output but because it's more fun because we can be surprised, I'm gonna restart my kernel and clear all the output. That way, all the cells will be blank and so we can see things better as they happen. All right, so I'm gonna start stepping through the cells. Recall it's a shift enter to step through a cell. In the first code cell, you can see I import numpy. We're gonna use that for arrays. I'm also importing the heat model. The import's a little clumsy because the package that we created wasn't necessarily set up to have access to this model but nevertheless, after running this cell, I'll now have access to the heat model. You can see then in the next code cell, I'm gonna specify the size of my plate. It's gonna have six rows, that's the y direction by eight columns. That's the x direction. And I'm gonna set up a conductivity of one in whatever you, let's say si, meter squared per second, I think. All right, and then so then we set up the heat object. So using the heat class, I specify the shape and the conductivity through the shape and alpha keywords. What's gonna come back will be my object reference M that will be used for the model. So now M is how I'm gonna control the heat model. So given the shape and the conductivity, you can see that the heat model solved for two other parameters, the spacing, so the default spacing is used for just one unit meters, if you wish, and the time step. So the time step is set by a stability criterion. So again, if I'm assuming si, it'll be 0.25 seconds. All right, so now I've initialized my setup. I wanna be careful, oh, I'm using words. I'm gonna be careful of those words. I'm gonna use those words later in BMI. I've started my model there, that's generic word. All right, so what does the initial temperature field look like? Here I'm using NumPy to set up an array of zeros. So you can see it'll be a six by eight array of zeros. And now, oh, and note that, let me step back just a second. This is probably something I should have pointed out when we were looking at the model code, but temperature is one of the attributes of the model. So this is the actual property that we are going to diffuse across the plate. You can see the way I've accessed it is as an attribute of the M object. And again, if any of this Python object-oriented programming terminology is confusing, please either open your mic and ask a question or ask through the chat. Okay, so back to where I was, I was gonna say we have a blank field, you can see it's all zeros. I'm using ASCII art here as well, if you will. All right, so the next step is I'm gonna set an impulse. So I have all zeros, but I'm gonna set a value of 100 at the location three, four. You can see now that our plate again is all zeroed out, but there's a spike in temperature near the middle. Now I wanna run the model. Here's where I call the advance in time method that I pointed out when we looked at the source code. So what this does is it does one time step, it advances one time step in the model. All right, let's see what happens. If I print the temperature field now, you can see, hey, there's diffusion. All right, so that initial peak of 100 has spread out a little bit by the process of diffusion. Let's see what happens if we keep going. So I've set some distant time, which is two. All right, so while you can see the code, while the time in the model, and again, this is something I maybe I should have pointed out as well earlier, time is kept track of in the model. So while time, while model time is less than this distant time, we're gonna keep advancing in time, one time step at a time. All right, so let's see what it looks like now in the future. Okay, so we've diffused away. The initial peak has been melted down to about eight, from 100 down to eight. You can see the way the boundary conditions work. So they're clamped boundary conditions, they're clamped at zero. So that means basically temperatures diffusing away from this peak, it gets to the boundary and it gets sucked out of the plate. Basically it's absolute zero at the edges of the plate. That means basically that the temperature isn't conserved. So you can see that if I sum up all the temperatures, the initially it would have been 100, but now it's down to 74. Okay, so this is just a demonstration of a simple model in Python. And what I would like to show next then is the same model, but run through its BMI. All right, and maybe before I do that, are there any questions or comments? Does this make sense so far? Just again, let Holler let me know. All right, so now I'm back on the page where I can show my second and last notebook for this discussion. So I'm gonna open up the second notebook, run the heat model through its BMI. Once again, I'm going to restart the kernel and clear all the output so that we can be entertained and surprised by the outputs. All right, so my goal in this example is to show that we have a BMI written for this Python model, this Python heat model. And I'm gonna do my example running the model through its BMI. So as before, you can see in this first paragraph, I have links again for the model and for its BMI. Let's take again, just a couple of minutes and browse the BMI code. Okay, so you can see here that in this file, I have a class here called BMI heat. And this class contains a series of methods. And you can see those method names, again, should look familiar. They should look similar to what Eric showed when working with PyMT. So there's an initialize, there's an update, there's a finalize, and then there's a bunch of others. And you can see that what we've did, what we did, what we've done, and actually, that was a Freudian slip because this is what Eric did. And I think I did a little tiny bit, but this is mostly Eric. You can see that we've just filled in code in each of these methods. So the code in each of these methods hooks into the heat code. So for example, if I do an update, so the BMI update method is meant to advance a model by one time step, you can see inside that update method, it's a single line, it's just calling the models advance in time method. Eric, I have a question. Let me go for it. So in this example, there's one non-field input, the conductivity, right? Yeah. Is there a BMI method that say you had BMI enabled model that's like get input or output var names? That's tell me all the inputs, what their units are and so forth. In this case, it would list like I need the conductivity. In this case, it's maybe a little trivial. There's one input. I can think of examples that might have 35 or 100 where having that interface could be really valuable. Maybe I've just missed where that is exposed. Well, it's okay. Let me see if I can repeat back and to make sure I understand your question. So you're saying it would be nice to have a function that would give back like the name and other information about a variable like it's type, it's units, for example. Is that what you're asking? No, I'm saying that they're so, there's sort of two types of inputs. There are things that are called like, that BMI calls var, right? Input var and output var. But then in the case of something like this heat model, there's another type of input, the thermal conductivity, thermal diffusivity type input, right? That needs to be provided to the initialize. Okay. And is there a way that looking through the BMI functions, I don't see where that information is exposed other than in like looking carefully through the template input files. But that takes you back to where you would be typically, which is looking through example input files. Yeah, this is something it seems like it would be handled in PMT, for example, through the parameters attribute, where in PMT we have access to all of these variables that aren't necessarily standard inputs or outputs, but are parameters of the model. And so we don't really have something like that in BMI. Okay. Just yeah, BMI just talks about the variables that are exposed for input or output. Yeah, which I might call state variables as compared with parameters, but sometimes parameters are not single values. Right. Okay, thank you, Mark. And we can talk more about this as well afterward too. Okay. Okay, thanks, Katie. That was Katie, right? All right, so, okay, so this is just a quick look at the code that is involved in making the BMI for the heat model. Okay, let's go back to the notebook and let's run through the notebook. Okay, so again, we'll call shift enter in a Jupyter notebook to advance. All right, in the first code cell, you can see I'm importing a couple of packages, so OS and NumPy once again. You can see here also I'm importing the BMI heat module from, or the BMI heat class from the heat module. So it's set up a little more nicely here. All right, so once I've imported the class, I can create an instance. So here I'm calling it X. So now X is the way we will control the heat model through its BMI. We can call just because this is something really simple, I like to use this at the beginning just to check that things actually worked. I'm gonna call the get component name method. It's a really simple one, and I feel assured then that the BMI is actually working. So that's good. So you can see it gives back the name of the model. All right, so whereas in the last example, so let me see if I can navigate here. So this is the first notebook. You can see that the way I set up the heat model is through keywords. I'm choosing a slightly different tactic this time, and instead using a configuration file. All right, so let me show this is again, this is kind of a bridge between the heat example and what Eric showed with PyMT, where with PyMT we use a configuration file. All right, so you can see this configuration file is in the YAML format. YAML, yet another markup language is a format that we love at CSDMS because it's simple and it's text-based. So you can see the configuration I'll use. I'll have again a six by eight element array. I'll have one unit between grid cells. The origin will be at zero and the conductivity will be one. This is actually the same setup that I used in the heat model example in the other notebook. Okay, so next then I want to use this configuration file to initialize my model. And again, that's a familiar name right there. And the initialized method is used in PyMT as well. And you can see this is Y because of the BMI. All right, so at this point now we have started the model. This is like getting into the car and turning the key to start the engine. Let's take a look at some information. So I could be looking for example, in my car at this speedometer. Hopefully I'm going zero when I start. You know, I could look at the gas gauge. I could see if my lights are on. So here's some information. I can get information about the start time, the end time, the current time, the time step and the time units. All right, so we start at zero and the current time is also zero. The end time was just up to a really large number. So we don't have to worry about running into the end. The time step is 0.25. And this again is the same thing as we set up in the first example. And this is the seconds. Okay, so along the lines of showing information about the model, next I wanna show the input and output variables. And this is what I was referring to a little bit in that discussion with Katie. You know, the idea is that BMI is interested in what goes into the model and what goes out of the model. The publicly available pieces of information. And you can see here, I have a link. I don't wanna take a huge amount of time here, but just to mention CSDMS standard names. In the first talk this morning by Julie, she mentioned GeoScience standard names. And so CSDMS standard names are actually a precursor and they kind of feed into GeoScience standard names. The idea of a standard name is basically a very descriptive name for a variable. I'll show you what I mean. So you can see that the variable called plate surface underscore underscore temperature is used as an input and as an output variable for this model. And the reason why this is very descriptive, so there's no confusion about what just temperature means because temperature could be many things. Next I wanna get some information about the grid that we're using. I mentioned earlier and you saw in the heat example that it's gonna be just a two-dimensional, regular rectilinear grid. So there are BMI methods that can be used to describe this. First of all, the grid identifier. As Eric mentioned, there can be many grids in PMT and therefore in BMI. Grids are numbered just as an index, so the first one is zero. There's one grid in this model, so it's gonna be index zero. The grid rank is two because it's two-dimensional. You can see the shape that we have set, the spacing and the type. Okay, so again, imagine you're in the car, you're getting information about what's happening with the car before you start driving it. Next, I'd like to do the same thing as you saw in the heat example. I'm gonna set the initial temperature field to be a bunch of zeros with a spike of 100, kind of near the middle. Maybe before I hit return here, just so you can see how this works. I'm using a numpy function to set the array and I'm using the BMI set value function to set the temperature using its standard name. So let's now get that temperature back out again and display it. So you can see, again, we have our six by eight element array with a spike in the middle. Let's advance the model by one time step. So we use the BMI update function and then let's take a look at what happened in the model. So we'll use get value to get the new temperature from the model and you can see, again, there's diffusion. So our initial spike has diffused away. And again, let's advance the model to some distant time, distant time being two. So similar to what we saw earlier, we're gonna check the current time, the current model time and keep advancing the model until distant time is reached. Then finally, well, pen ultimately, we can view the results. So again, that initial peak has melted away. The boundary conditions are clamped so it's zeros around the edges. And again, the temperature isn't conserved. Finally, we can finalize the model, freeing up any memory. Okay, so at this point, maybe you're looking at me in the little window here in my share screen and thinking, Mark, why did you do this? It's the exact same thing. Look, it's the exact same thing we did in the heat model. The numbers are even the same at the end. What did we gain by putting a BMI on the model? It's the same thing, but that's exactly the point. All right, so the cool thing is, is that we have run this model with a standardized set of functions. And as Eric pointed out earlier, once you've seen one BMI, you've seen them all because it's all the same set of functions. So the really cool thing about this is that I get excited about and quite sure I can convey well is that we have a standardized interface on this model. I don't need to know anything about what's inside the model. All I need to know is how to run the interface. So this is the cool thing about BMI. I think that's it. All right, so are there any questions or comments? I'm gonna stop sharing. I see lots of stuff in the chat. I hope on Eric is doing a bunch of stuff. Yeah, all right. Okay, so again, take a couple of moments if you wanna ask a question in the chat. That's cool. Otherwise, or you can open your mic and ask a question. Otherwise, I know Eric had a small break scheduled. Eric, is this a good time to take the break? Yeah, we could take a break. Yeah, there was a question mark about being able to print out all the variables with a single function call, I think it was. Oh yeah, like we're printing them out. I'm not sure which line that was. Yeah. I may be wrong, but I think this refers to the printing out grid ID, grid rank, grid shape, grid spacing, grid type, and so on. And that seems kind of tedious to have to do all that one at a time. Yeah. I'm picking up on Eric's answer in the chat. If you wanna interface that's language agnostic, you have to go with the sort of the least common denominator among languages. And of course, languages like C and Fortran are pretty basic as compared to something like Python, which has dictionaries as data structure. And so that's why some of the BMI functions can seem a bit basic or primitive. They very deliberately don't take advantage of high level languages like Python because they need to be able to speak Fortran and C and a variety of different things. Well, I just saw a question from Uriel. So she asked, what dimension does distant time have? Is it seconds or time steps? So it'll be in seconds. So it'll be a time as opposed to a change in time. Okay. Well, it's a 2-12. How long was the break, Eric? Well, I think we had five minutes. Five minutes. Is it too cheesy to say come back at 2-17? Yeah, we could do that. We could say 2-20 probably. Okay. If we keep talking, it'll be five minutes then. Yeah, exactly. All right. Okay, so let's move back at 2-20. We'll see everyone in just a little bit. And we'll hang out if you have any questions as well. Yeah. All right, we'll start in just a little bit when Eric gets back. I'm here. Oh, you're here? Oh, sweet. Hey, Eric. How you doing? I'm doing all right. Got enough coffee? Well, my cup is empty. Yeah, that's unfortunate. Yeah. Eric, you want to take it away? Yeah. Did we say, I forgot what time we said. Are we ready? Are you all back? 2-20, yeah. Okay. Sure, yeah. There were a couple of questions that maybe we, I thought of a couple of things over our break about BMI. So the BMI is written to be as easy to implement as possible. So we want to make it easy for model developers to add BMI's to their models. As such, like, and as Greg mentioned, we wanted to make it a language agnostic so that we can't use fancy language specific syntax. And so that results in a BMI that looks a little bit clunky sometimes. So you have to make each of those calls about the get var units and grid and all that stuff that we were talking about before the break. And that's by design. And that's another reason for PMT to make using a BMI-enabled model or component easier and more natural in a language like Python. For instance, in one function call, you can get all the information about a variable or a grid, just in one single function call. Another question was about how BMI handles grid mismatches and the answer is that it doesn't at all. We could have designed BMI differently where we could have had a BMI where one component could ask another component to get its variables and map them or do unit conversions and then return them. But that put way too much, it would put more work on the developer of the BMI. So that every BMI model would have to provide all this grid mapping capability, which seemed just to be way too much work. And so all of that stuff is left up to the framework. So a model that has a BMI only reports about itself. It says, you know, this is my grid, this is what it looks like. If you can do something with it, great. If you can't, you can't. But it's not up to the BMI or the BMI implementation to do those things. And then the other thing was to get a model into PMT, there's a few things that we need to do. One is to run it through something that we call the babelizer, which wraps it. So a model written in some other language wraps it in such a way that we can bring it into PMT. And then the other thing was that we need to provide some template input files. And this actually gets a little bit to Katie's question too. So how do we create these input files with all these input parameters? So these are the input parameters that don't change with time. So in the setup method, they're keywords. And so you need to provide a set of input, you need to describe those input parameters and describe the input files that they get placed into. And I can show you a quick example of what that might look like. So I'll just try to do this quickly. We can talk about it at some other time. So, but just quickly. So this is what some of the metadata files look like for the plume model that I showed earlier and how they would be used to get a model into PMT. So you could look at the parameters.aml file. This describes all the input parameters for a particular model input file. So the plume input file. So for every input parameter, it has a description of what it is, its units, a typical range, a default value, the data type. And then what a template input file would look like. Well, here's the plume flood file. So it looks like it would be, it would look like a regular input file except it has all these double curly braces. And so the setup method knows to where to put the keywords that you passed to the setup method into the input file. And so that's how the input files get created. So those are the a couple of steps that you'd have to do to get a BMI-enabled component or model into PMT. One other comment is, we support four languages, C, C++, Fortran, and Python, but there are BMI's that are written for other languages as well. Like I think some people at DELK have written a MATLAB BMI and an R BMI. Julia. Julia, right. I've got my old, my Java one that I haven't looked at a little bit, but that's still valid. Yeah. A BMI itself is language agnostic. You can write a BMI for any language. PMT though, to get into PMT, we need to have a bridge that goes from that language to Python. And I think there are for, there is one for Java. So we just have to implement it. Yeah. All right. Let's close up our stuff there. So if we go back to, well, maybe we'll go back to your project page and then I will click on the, launch the PMT notebooks again. And now I'll go through two examples. It's gonna, they're gonna have to be a little bit quick, but we can, I think we can do it. So I'm gonna run through a standalone PMT model and see what a real model looks like in PMT and how you would run it. And then we can couple a couple models and you can play with either one of those. So we'll run a standalone model in PMT. So the model that we're gonna run in PMT here is called HydroTrend. It's a 2D hydrologic water balance and transport model. So it's a lumped model where you specify a bunch of parameters about a basin and then it will predict the sediment discharge that it's out. If you want to describe it at all, Irina, you're kind of the expert on this. I mean, it depends a bit on, I think it might be cooler for people to play with the Jupyter notebook than to get too much detail about HydroTrends. But maybe I can say a few words about it. Yeah, just quickly, we don't have too much time actually. I know, I have like two or three slides in that I'll share them like real quick. Add some to you. Okay. Do you see my slides? Yes, it'd be good. So the model that Eric's talking about is called HydroTrend. And the way you need to think about this is it's sort of a simple mass balance hydrological model. And so it's climate-driven and it transports water through like a set drainage basin. But then what it really is intended to do is to like predict at the river mouth. So at the outlet of a certain basin, what kind of daily water and sediment flux comes out of it. It can be applied everywhere in the world for a drainage basin that you have both topographical characteristics for as well as these climatological drivers. And it's been used that way too in like pretty much on every continent. There are some applications. And so the things that in a notebook you can start changing would be like the input precipitation or like the temperature in your drainage basin. And we're using a default basin in the example that Eric will be showing, which is for the Waiapoa basin in New Zealand. So there like your geometry in your drainage basin is all set up already. And so these are like the geometry factors. And then maybe to super quickly say, like it's a water balance model. So it calculates the components of like rain, groundwater, snow, navel precipitation and even put transpiration to like for each part in the drainage basin to determine what the water, outcoming water is. And then it uses this long-term suspended sediment load equation, which is completely empirical and comes from analysis of like many drainage basins in the world to predict suspended sediment load. And in the notebook, like some of the things that you can play with have to do with the controls that came out of the regression basically that established this equation. So like you can like change like lithology or trapping efficiency, et cetera. So maybe those are like just a very simple background notes to this model. Thanks, Irina. I'll hand it back over to you. Sure, yeah. All right, so we're gonna run a hydrogen example. And so you'll have a pretty good idea of how this all works because it's, you know, if you know how to use one model, you know how to use them all. So we do some imports, we import the hydrogen model and then we can run some base cases. So we're gonna do the setup. We're gonna run it for a hundred years. So the run duration is one of the input parameters. And if, and again, you could do help on hydrogen and if you scroll down to the parameter section, you would be able to see that somewhere down here, hydrogen takes a lot of input parameters. The run duration is one of the things that you could specify there. So if you wanted to run this with some other input of parameters, you could just add those as keywords in the setup method. And then we will initialize and the model. So now hydrogen's ready to go. And we can print out the time, the current time. So we're at the beginning. We're gonna run it for a hundred years. Now the input var names method. So this is like the method that Mark showed for the BMI that's called get underscore input var names. In PIMT, it's a little bit shorter to match. So it looks more pythonic. So in hydrogen, you'll notice there are no input var names. So this means that once hydrogen starts time-stepping, you can't change any of the inputs. All of the inputs are specified in the setup method or in the input files. But there's a bunch of output var names. And as Mark said, they're all specified in the CSMS standard name format. And so some of them have unusual names that are hard to figure out. But so like, let's see here what's one. Channel exit water sediment bed load mass flow rate is simply bed load flux. So it takes a little bit of knowing what those names mean, but they need to be specific so that you can couple them to other models and know what variables you're actually coupling. So now we're gonna do some time-stepping. So again, this is typical of a BMI model in PIMT and how you would time-step through it. We're going to calculate the number of time-steps, allocate some arrays. We're gonna save here the time series of discharge. And then we're gonna run through a series of daily, yeah, that's right. So a hundred years of daily discharge we've just degenerated. And so it's as easy as that. So now we can plot it and there's our discharge for the hundred years. Now we've got some, we had some exercises here that you could, so you could analyze some of the data. But since we're running out of time, I can just kind of go through them. So we have discharge, sediment discharge, bed load flux, and we can calculate the average over the hundred years with typical NumPy functions because the PIMT just returns the NumPy arrays. So it returns all the data as NumPy arrays. And then we use the var units BMI method. And we can do things like calculate the year that contains the maximum discharge and what its value is, annual means. And then so we had a question here where we were going to have you all, if you, we can still do this quickly if you'd like. So what I've done here in this cell, I've pasted all of the code from each of the cells into one and sell it. So you can just play with what a hydrotrend run in with just running the single cell. And so the question was, what happens to river discharge of suspended load and bed load if the mean annual temperature in this case increases by four degrees Celsius over the next 50 years. So if we just ran the cell right now, we would just use the defaults again for 100 years. But if we wanted to change this, the increase in temperature per year by four degrees Celsius, we'd have to figure out how to do that. Now we know that, so if you remember from up top with the input var names, there are no input var names. So we can't do this dynamically as it's running. So that means we have to do it in an input file. So we do this through the setup method. So we're going to have to add a keyword to the setup method to enable this. Now what that keyword is, I don't remember, but if you could get help on it somewhere down here, there'll be a keyword for this. And it may be this one here, change in mean annual temperature. So if you put that into the setup method, I guess we can try this, then you should be able to run the cell. So I think the units it said were degrees per year, so we can try just four and then run it again. Well, apparently that didn't work. Yeah, you're changing it by like four degrees a year. Like it goes too fast over a hundred years. Okay. And it does like that parameter does have boundaries, so like it would... Oh, gotcha, gotcha. Yeah. That makes sense. It'll be like 0.04. Yeah, well, we can try that. As I mentioned before, when you run this, a lot of times the kernel will crash because you will crash one of these models that doesn't have the same error handling as other models. And then you can also try different values in the setup method and you can run your own simulations if you like. Hey, Eric. Yeah, sure. Can you show that the error messages is in the terminal? Like if you open up the terminal, because that's turned out super handy when we were using these. Okay, yeah, sure. I'll see if I've got that. Well, no, I'm not because I'm running on the Jupyter Hub. I don't think I can. Oh, okay. Oh, I thought there was a way to open up a terminal there too. Did Mark use that? No, maybe not. There. So now we ran it with a valid change in mean annual temperature. And then so you could add different values, different keywords. You don't have to specify just one. It could be any number of those that are in the parameters file here. And depending on the value you choose, you may crash the model just like you would if you were running it straight from the terminal. But hopefully, hopefully you won't. Anyway, so that's the hydrogen model. And that's how you would run a simple model 1D or a simple pi and t model without coupling it. But you can continue playing with this if you like. And we're going to leave the Jupyter Hub up running for another day or so. Well, we're going to leave it up for quite a while. But we're going to leave it up for the next day or so at this level so that lots of users can come and run their things. But I will go now to a coupling example. That's going to couple two models. It's going to couple a coastline evolution model. And so we're going to run that. And then we're going to couple it to a waves component so that we can change the incoming wave climate at this time and see how that affects long-shore transport. So we're going to start with the same imports as we normally would. And so I'm going to show you the coastline evolution model right now. And so it's called CEM. So the coastline evolution model, so this is a plan view model. So it's going to be a rectilinear grid. And there's going to be a coastline with a sediment input. And so it's going to slowly build out a delta. And then that delta will be acted on by waves coming at various angles. And there'll be a long-shore transport. So that's a quick description of the CEM model. And now we can start stepping through it. So the input var names for this, so there's now there are some input var names. So as the model advances, we can change what we have here. We can change the wave height, the wave period, the angle that the waves are coming in. Those are the main inputs to the CEM model that we can change. And then also there's also some outputs, like the main one being sea water depth. And just like all models, you use the input and output var names to get those. And you'll notice there's this one long name here that we can examine. And so this is the incoming angle of the waves. And then you can get the var type, the var units, the grid, and the node count. And let's see if I insert a cell above. What should I be able to do? So CEM, so some of that. So this is a CEM.var, if I'm not sure. Oh, because it's not an output, so we can do. I'm not sure what's going on there. Any case, well, that's strange. You can, any case, you can get the what using the typical BMI methods, you can get all the data for each of the variables. And then as before, the lifecycle of the component. So it starts with the set up method. And then the initialize, you call initialize. So here we're going to set up a model with 100 rows and 200 columns. And that spacing of the grid cells is 200 meters. So now we've set that up. All the memory's been allocated and it's ready to time step. The input variables, there are some input variables as time. So we're going to set some of the inputs for the incoming wave angle and period and height. We're just going to set them to be constants right now. But we can, you can change them to something else later if you'd like, or right now if you'd like. I've set, so one thing to remember is that the wave angle is going to be in radians. So I've set this to be 0. You could change it to something else, 45. 0 means that the waves are coming straight into the coast. So we're going to set those right now. So the grid for seawater depth, so var grid is 2. So we know that the, so again, every very, every parameter, every input var or output var in BMI models to find on a grid. And so in this case, it's grid ID 2. So we can get some information about grid ID 2 by calling the BMI methods. So grid type, grid number of dimensions, shape, spacing, the origin. So this is a uniform rectilinear grid of rank 2, so that's two dimensions. The shape is 100 rows by 200 columns. That's what we specified in the set up method. And the spacing is 200 meters by 200 meters. So that's a grid that we're going to be looking at. And then I've got a convenience function here to plot up grids in time. So as we start to step through the model, we can see easily plot the output. So now we're going to look at just the initial bathymetry of the model. So we allocate variable Z, which is just a numpy array of the correct shape because we used the BMI method. Then we get the value for C water depth. So this, again, this is the BMI getValue method. And then we specify a keyword argument called out, which tells getValue where to put the output. If you didn't provide this, we've allocated a new array and returned that. And then we're going to plot the Z values to, or send them to the plotCost function. So you can see that we start out with a flat coastline that's going into the ocean. And again, we can plot information about each of the input variables. So we know that the, so we can get the units. So we know that's units of radians, for instance, for the incoming wave velocity. The var grid, so we already got, so this was the bed load mass flow rate. So this is going to be the input sediment, the sediment that we're inputting into the model. And this also is on the, on grid ID too. So it's going to be on the same grid as we just plotted. And so it'll have the same dimensions in spacing. Now, because the bed load mass flow rate is an input variable, we need to set it to be something. And so we'll set it to be 750. And that's, I think it's kilograms per cubic meter or per second. Now, one thing to notice here is that the, the bed load mass flow rate is a grid. So it has a grid that has the same size as the, as the bathymetry or the elevations. So you can specify multiple sources of sediment. And what we're going to do here is specify sediment entering in the middle of the grid right here. And it's just going to go straight across the coast and into the ocean. So it's very simple right now. And so that's where we've specified it here. So QS is our input sediment, row zero and then column 100. So again, it's 200 columns wide. So it's just going to be right in the center and we're going to have it go straight out. So the set value method is how you set parameters inside the BMI model. Okay, now we're going to run the code for 3000 days. And then each time step, we're going to set the sediment input. And then we're going to look at the output after 3000 days. So it should take, I think it just takes about 30 seconds to run. So again, we have sediment going straight out, waves coming straight in at a constant, with a constant sediment supply. I think maybe a couple of people are running it because it's taking a little longer than it usually does. It's nice to know the limitations of our Jupiter hub. Yeah, yeah, that's, it's running pretty well actually, but a little slower, maybe 50% slower. Okay, so now we have got some new outputs. And so we've called the get value at the end of the loop for the seawater depth. And so we can plot it up and see what we've done. So nothing too exciting yet, but we've got now a little delta that's prograding straight out with waves coming straight in. So I think that's more or less what you would expect in such a case. So now, if you guys would like, you can play with a model yourself. So again, when I've done here, I've copied all the steps that I took above into one single cell. So you can just run that cell over and over again with different inputs. You can try different things. You could modify the wave energy. And there's a balance between the wave energy and the sediment load. And that really determines what the delta looks like. So you could change that. Or the wave angle, the incoming wave angle you could change. You could make the incoming wave angle be pulled from a distribution. So you can feel for you to change those. You can move the river mouth around over time. So what I have down lower is an example where the river mouth is moving back and forth through random walk. So each change in the river mouth horizontally, the change in the location is drawn from a normal distribution. It's a given standard deviation. So you could put that in and then run that and see what happens. So I can leave that up to you to do yourself. But since it's a random time, feel free to do that while I'm continuing talking or we'll stay on after three o'clock and we can go through that. I've added a couple. This Evulse River does the random walk. So if you were to do that example, you would have to use the Evulse River function. I think, Eric, this is a good example of a model where you don't control the errors that it will throw. Oh yeah, for sure. So especially with playing with really weird wave angles coming in, if people do that, that's like a known thing to crash the coastal evolution model well. Yeah. And it'll definitely, if you give it unusual parameters, it'll definitely produce an unusual looking delta. There's, if you guys are running this, you may encounter what we call the Christmas tree delta. And it's got this weird delta with these spits coming out. It's very unrealistic looking, but you can get it if you use the wrong set of input parameters. Although it doesn't actually cause the model to crash in that case, but yeah. So I'm gonna skip the implementing any of these examples. Well, wow. Yeah, we'll come back to it. We'll get to the coupling example of just changing, coupling it to a wave model that provides incoming wave heights and periods and angles that matches more closely what the original CEM paper was looking, or one of the original CEM papers was looking at. So in this case, we're gonna import, so we've already imported the CEM component. And now we're gonna import what we call the waves component. And then as before, we're gonna run the setup method for waves, we're gonna run the initialize method on the waves, and then we're gonna time step it and using the waves.update method. So we're gonna set the angle asymmetry and angle highness factors. And these are just two very particular parameters to this model. And then we're gonna run it. So now we've generated, I guess a thousand time steps of data. And then we can just see what it looked like. So this is an unusual distribution where they were trying to examine how high incoming wave heights affects the shape of the delta. So you can see here, so in the x-axis, it's degrees. So again, zero means the waves are coming straight in. And then so there's a large number of waves coming from one direction. Coming from one direction more than the other. So again, I've copied all the steps needed to run the both the CEM and the waves component into one cell that you can run. And so you can see it's pretty much the same as it was before. We import the models, we instantiate waves, we run it setup method with the two parameters that we're going to use, and then we initialize it. Then we do the same thing with CEM using the same parameters as we used before. And again, we set the, now we're setting the queue at the sediment input at the same place on the center of the grid, it's going straight out. Then we advance it through time. And at each, now that at each time we're going to have to get a new wave angle or new wave angle rather from the waves model. So to do that, so for every time step in the loop we run waves one time step, then we use a get value method to get the value at that time step. And then we call the set values on CEM. So we run the waves model, we get wave angles then we set them into CEM, and then we update the CEM model by one time step. We do that for 3,000 days. So again, we can run that and then we'll get a different looking delta than we did before. It'll be more asymmetric, or it should be. And so feel free to play with any of those parameters. If you crash the kernel, that happens. You just have to restart it and try again. All right, so it's completed. Then we've created a little bit, a little delta and it's just starting to build out and I think it's gonna start building spits pretty soon. So, but that's what it would look like, a coupled example. This is a simple one, but the pattern is the same. You run the setup and the initialize method for each of the components that you're wanting to couple. You then have a time loop and you update each of the components in the order that you want. You get the value from one component then you set the value into another component and you go back and forth like that for every time step. And because all the methods are standardized, it's been very similar for any model coupling that you want to do. This is just a simple one. And feel free to just play with it as you like. And as I said, we'll continue, we'll hang out here if you would like to play with it some more. But that's basically how you would couple several models together in the N-PyM team. That's it. Good job, Eric. We're like perfectly at three o'clock. Yeah, I'm sorry I rushed it at the end, but like I said, we can stick around. Mark and I got a little too chatty, I guess. Apologies. Hey, Eric, I was wondering, shall we pull up the PyMT read the docs side and so that people if they wanna like continue on or like play outside of these examples? Sure. Yeah. So we can, so if you go back to the project page, we should have some of these links here. So the Python modeling toolkit. So under links, that should, I think point to the read the docs page. So that's one way to get there. Otherwise you can remember that it's just pymt.readthedocs.io. And so this will take you there and it'll have some more milk books, some descriptions about how to use PyMT and some of the models in PyMT. Hey, I mean, the other thing that I think we are, because this is such a short like demo kind of workshop, we don't go into like, how would you install this on your own machine? But the notes here in the PyMT read the docs should help a lot with if people wanna like do a call and I install all of these tools and like run them on their local environments. Yeah. Oh, we've got a good question here in the comments. This is from Sarath. Is there a method to change parameters of coupled models without re-initializing the model? You would normally have to re-initialize the model. I think if I'm understanding the question correctly. Right, that's what I was thinking again. Yeah. Good, I'm glad, Rachel, I'm glad you asked that. I was gonna say there's one thing that you get out of this model or this talk today. It's about the progress bars. There's this package in Python called TQDM. TQDM. Apparently it's an acronym for something that. Anyway, so if you have a loop and you just wrap that loop like this, it will generate a nice progress bar so that you don't have to just like print out the timestamp every time so that you know that it's doing something. Although it often is completed while it's stuck at 99.9%. Yours does? Yeah, roundup, floating point error. I know, or it's like a counter. Yeah, yeah. Yeah, they used to float instead of an int in their counter. Oh my goodness, ah. Something, or like they didn't start at zero but at one or like whatever. Yeah, Vaclav has a question about update versus update until. Yeah, that's a good one. Update is a BMI method and it's required. So it will update a model just one time step, whatever the time step is for that model. However, it's often useful to have an update until method for models. Most models can implement this and if they can, they really should. And so the update until method would take as one, so the update method doesn't take any argument. The update until method will take a time argument. So when you're coupling two different models, you would ideally like to have them in sync. So you would say, okay, I'm gonna update this model until some time step, which is perhaps the time step for the other one, that would be a good idea. And then so that's the difference between the two models. However, some components just for whatever reason, they can't implement that. And if they can't, they can't and that's just the way we have to deal with that. But that's what the update until method does. Eric kind of wanted update until we go away. I saved it. No, I shouldn't go away. But it's optional. And some of these methods, some of the BMI methods are optional. You don't have to implement absolutely every single one of them if your component just doesn't do some of those things. Well, I mean, you have to implement them, but you could just return. Yeah, that's right. Yeah, you have to tell PMT that you have not implemented that. Right. So yeah, so that's how it would work. We wanna make this as easy as possible for people to implement. Yeah, you may remember we saw an example yesterday of a model that had variable time steps. And so if you were running that and coupling it with a model that is fixed time steps, you might find it easier to use update until so that you're telling a model, look, take as many steps as you need until you get to exactly three weeks from now and then stop, pause there. Yeah. I enjoyed Rachel's comment in the chat. Oh, Arena, I have a question. Didn't Stephanie make a tool that allowed you to take some watershed and make it into a hydrotrend hypsometry file? Oh, Arena, I think you're muted. Yes, I think that's true. I haven't touched it or like it's not working with the current versions of what our tool sets, but I do have the GitHub repository of it and like we can make this happen. It was one of the things that came out of the class that I was teaching with some of the tools too, because it provides flexibility for people. It uses a hydrosheds global dataset of drainage basins like a global course resolution DEM and then automatically spits out for a certain drainage basin while there's the hypsometry file. Nicholas had a question about that. Yeah, I saw it. It's not without like a little bit of modification it would not be implementable straight away. I put up for people who are still long here, I put up the link to our helpdesk to you or like to show you that there's like this option if you really start playing around with these tools or these things and you encounter problems then CSDMess is running a helpdesk on GitHub. And so you just go, I mean, I just Googled CSDMess helpdesk and it got me to this address pretty quickly and then you can submit issues there and the advantage of doing it that way instead of like emailing us directly is that there's a record of the questions and people can start mining these. And like Mark set this up, like I don't know, a couple of months ago already but we're trying to point people to it too. Rina, can I take over? Yeah, go for it. There's one thing I want to show before people go. It'll be fast. To get the very, there must have been a typo earlier when I couldn't do this but if you have an instance of a model like CEM there's a dictionary called var and the keys of the dictionary, the variable names. And you can run that and you can get information about each of the variables as well. So you can get the units, the grid, whether it's input or output in the node location. So I don't know. I think I had a typo before or something anyway. I wasn't going crazy and it really is there. Sorry, Rina, if I interrupted you. No, you did not. Okay. And there's all the variables for the CEM model. Okay, anything else? Are we all done? I think so unless anyone wants to ask more questions. All right. People are slowly dropping off. All right. All right, well, let's call it. All right. Well, thanks everyone. Thanks everybody. Thanks everybody, it's still here. A lot of fun.