diff --git a/examples/README.md b/examples/README.md index e680448..253bd14 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,3 +19,4 @@ standalone and through a Basic Model Interface. * [explore-model.ipynb](./explore-model.ipynb): Explores the parameters and initial conditions of a `DiffusionModel` instance. * [run-model.ipynb](./run-model.ipynb): Shows how to run `DiffusionModel` standalone and make a plot of the results. +* [run-bmi-model.ipynb](./run-bmi-model.ipynb): Explores and runs `DiffusionModel` through its BMI. diff --git a/examples/run-bmi-model.ipynb b/examples/run-bmi-model.ipynb new file mode 100644 index 0000000..39b4532 --- /dev/null +++ b/examples/run-bmi-model.ipynb @@ -0,0 +1,547 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "154382ab-1ad1-42de-8dfc-2b7f9fe3d368", + "metadata": {}, + "source": [ + "# Run `DiffusionModel` through its BMI" + ] + }, + { + "cell_type": "markdown", + "id": "cfd925a2-48a3-4370-990b-2fc2c2e3b13a", + "metadata": {}, + "source": [ + "`DiffusionModel` models diffusion in two dimensions with an agent-based approach, randomly moving particles zero to one grid cell in each time step.\n", + "Wrapping `DiffusionModel` with a [Basic Model Interface](https://bmi.csdms.io/) (BMI) lets you you control the model through a standard set of functions so you don't have to know the details of how the model is run." + ] + }, + { + "cell_type": "markdown", + "id": "1d63b202-5550-4b78-a75f-645024dec654", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "id": "786627d4-caea-4ddd-9737-25de4f4af89e", + "metadata": {}, + "source": [ + "View the model configureation file, `config.yaml`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f43330d-c051-4108-9f5d-1f3ef0bc6779", + "metadata": {}, + "outputs": [], + "source": [ + "!cat config.yaml" + ] + }, + { + "cell_type": "markdown", + "id": "6f1624e4-cd52-479c-999d-085648d508ae", + "metadata": {}, + "source": [ + "Import the Python libraries we'll use below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e5c2dff-fd7b-40ce-a634-811bf0262af2", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "id": "0f7758b5-86fe-4b97-bf38-49f023fca226", + "metadata": {}, + "source": [ + "## Initialize the model" + ] + }, + { + "cell_type": "markdown", + "id": "16224941-a041-4ea0-ad4a-8a67ef5460c6", + "metadata": {}, + "source": [ + "Import the `DiffusionModel` BMI." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c4b14ac-2233-44da-8a1d-e16affd1a4ff", + "metadata": {}, + "outputs": [], + "source": [ + "from diffusion import BmiDiffusionModel" + ] + }, + { + "cell_type": "markdown", + "id": "f14043e4-e43f-45ed-8fcc-31d8e8fac78a", + "metadata": {}, + "source": [ + "Make an instance of the model through its BMI." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c6031e6-987b-4ed3-b294-ee710d224e0a", + "metadata": {}, + "outputs": [], + "source": [ + "m = BmiDiffusionModel()" + ] + }, + { + "cell_type": "markdown", + "id": "c2f6ecff-6129-49cc-84fe-fb5569dc521d", + "metadata": {}, + "source": [ + "Get the name of the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3093b2d2-b77b-40c6-9c49-519ac0fd493b", + "metadata": {}, + "outputs": [], + "source": [ + "m.get_component_name()" + ] + }, + { + "cell_type": "markdown", + "id": "86294c45-cd5f-4dce-a274-8edd217ffe67", + "metadata": {}, + "source": [ + "Initialize the model using parameter values from the configuration file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f95a4abd-2176-4f0d-8753-e6721c8ff14d", + "metadata": {}, + "outputs": [], + "source": [ + "m.initialize(\"config.yaml\")" + ] + }, + { + "cell_type": "markdown", + "id": "074355d1-5863-44d2-b3de-c3451bc420c5", + "metadata": {}, + "source": [ + "## Get model information" + ] + }, + { + "cell_type": "markdown", + "id": "953919e4-868b-437b-a0be-db2b8fc7af07", + "metadata": {}, + "source": [ + "List the model's input and output variables (also called \"exchange items\")." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6084fcaa-d8d7-41bd-a6dd-acbc6a7c92a7", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Number of input variables:\", m.get_input_item_count())\n", + "for var in m.get_input_var_names():\n", + " print(f\" - {var}\")\n", + "\n", + "print(\"Number of output variables:\", m.get_output_item_count())\n", + "for var in m.get_output_var_names():\n", + " print(f\" - {var}\")" + ] + }, + { + "cell_type": "markdown", + "id": "490b059b-089b-4feb-a36a-e6ff04d1b15a", + "metadata": {}, + "source": [ + "The BMI exposes one output variable, `model_grid__histogram_of_agents`, that maps to the `histogram` variable in the AgentPy `DiffusionModel` model.\n", + "The long variable name is an example of a [CSDMS Standard Name](https://csdms.colorado.edu/wiki/CSDMS_Standard_Names).\n", + "\n", + "Get more information on the `model_grid__histogram_of_agents` variable." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91426748-15b6-47b0-9c81-99b388246bb8", + "metadata": {}, + "outputs": [], + "source": [ + "var_name = m.get_output_var_names()[0]\n", + "print(f\"Variable {var_name}\")\n", + "print(\" - type:\", m.get_var_type(var_name))\n", + "print(\" - units:\", m.get_var_units(var_name))\n", + "print(\" - itemsize:\", m.get_var_itemsize(var_name))\n", + "print(\" - nbytes:\", m.get_var_nbytes(var_name))\n", + "print(\" - location:\", m.get_var_location(var_name))" + ] + }, + { + "cell_type": "markdown", + "id": "816a9999-c153-41af-b812-f7d07fd27deb", + "metadata": {}, + "source": [ + "Note that a unit of `1` is shorthand for a dimensionless variable in the [UDUNITS](https://www.unidata.ucar.edu/software/udunits/) library used by CSDMS Standard Names." + ] + }, + { + "cell_type": "markdown", + "id": "85fc8038-ac68-43fa-ba61-c923c5b70f06", + "metadata": {}, + "source": [ + "In a BMI, all variables are defined on grids.\n", + "\n", + "Get information about the grid used by the `model_grid__histogram_of_agents` variable." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d28d22f-def8-4144-8f67-7874dcf1e125", + "metadata": {}, + "outputs": [], + "source": [ + "grid_id = m.get_var_grid(var_name)\n", + "print(\" - grid id:\", grid_id)\n", + "print(\" - grid type:\", m.get_grid_type(grid_id))\n", + "grid_rank = m.get_grid_rank(grid_id)\n", + "print(\" - rank:\", grid_rank)\n", + "grid_size = m.get_grid_size(grid_id)\n", + "print(\" - size:\", grid_size)\n", + "grid_shape = np.empty(grid_rank, dtype=np.int32)\n", + "m.get_grid_shape(grid_id, grid_shape)\n", + "print(\" - shape:\", grid_shape)\n", + "grid_spacing = np.empty(grid_rank, dtype=np.float64)\n", + "m.get_grid_spacing(grid_id, grid_spacing)\n", + "print(\" - spacing:\", grid_spacing)\n", + "grid_origin = np.empty(grid_rank, dtype=np.float64)\n", + "m.get_grid_origin(grid_id, grid_origin)\n", + "print(\" - origin:\", grid_origin)" + ] + }, + { + "cell_type": "markdown", + "id": "5ecd6437-c779-4bbf-852d-ed1d9eae4e11", + "metadata": {}, + "source": [ + "Get time information from the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37fc7faf-cfc9-40be-b0c3-ec3d0a68c33d", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Start time:\", m.get_start_time())\n", + "print(\"End time:\", m.get_end_time())\n", + "print(\"Current time:\", m.get_current_time())\n", + "print(\"Time step:\", m.get_time_step())\n", + "print(\"Time units:\", m.get_time_units())" + ] + }, + { + "cell_type": "markdown", + "id": "b24f52a1-03a9-46f7-a131-37a144e9111e", + "metadata": {}, + "source": [ + "Note that while the model has been initialized, it is still at time step zero." + ] + }, + { + "cell_type": "markdown", + "id": "6fa1b57f-5ebf-480c-88fd-2aacd6035cf8", + "metadata": {}, + "source": [ + "## View the initial model state" + ] + }, + { + "cell_type": "markdown", + "id": "9c75dca9-9a4e-42a3-b4c9-8af3166d4d74", + "metadata": {}, + "source": [ + "Get the initial distribution of particles on the grid." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "355551c3-895e-4182-8ebd-63293faa4ace", + "metadata": {}, + "outputs": [], + "source": [ + "val = np.empty(grid_size, dtype=m.get_var_type(var_name))\n", + "m.get_value(var_name, val)\n", + "print(f\"Particle distribution at time {m.get_current_time()}:\")\n", + "val" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "b109213b-7ccc-4045-a8da-ac0a4e4cc814", + "metadata": {}, + "source": [ + "Note that the particle distribution is returned as a one-dimensional NumPy array.\n", + "\n", + "As a metric, report the total number of particles on the plate." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed9a52b8-2dbb-4563-ab73-27dddd8d7dd7", + "metadata": {}, + "outputs": [], + "source": [ + "val.sum()" + ] + }, + { + "cell_type": "markdown", + "id": "e49cc52b-c52a-46f1-aa0e-9c354dd5eb75", + "metadata": {}, + "source": [ + "Visualize the particle distribution using *matplotlib* and a helper function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f592e545-f179-4255-99f8-5258b3174375", + "metadata": {}, + "outputs": [], + "source": [ + "from diffusion.vis import histogram_colorbar_plot\n", + "\n", + "fig, ax = plt.subplots()\n", + "histogram_colorbar_plot(m._model, ax, fig)" + ] + }, + { + "cell_type": "markdown", + "id": "507b3309-de8e-48b2-87a2-dd75ca597a72", + "metadata": {}, + "source": [ + "Note: to use *histogram_colorbar_plot* we access the model reference through a helper attribute defined in the `DiffusionModel` BMI." + ] + }, + { + "cell_type": "markdown", + "id": "1219c7a4-f55e-4b71-842d-842799895e65", + "metadata": {}, + "source": [ + "## Run the model" + ] + }, + { + "cell_type": "markdown", + "id": "34d162a3-b9e1-40cb-b5d2-f8fe5528e1fb", + "metadata": {}, + "source": [ + "The model is currently at time zero.\n", + "Advance the model one time step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1eb58be6-fc89-4e9b-b261-f6dc95e4b240", + "metadata": {}, + "outputs": [], + "source": [ + "m.update()\n", + "print(f\"Time: {m.get_current_time()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "00d4d54f-1613-4192-ae6c-74e7b34f39f9", + "metadata": {}, + "source": [ + "Have the particle locations changed?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38d09f45-6c93-4d92-a41b-fe6dd941cd50", + "metadata": {}, + "outputs": [], + "source": [ + "m.get_value(var_name, val)\n", + "print(f\"Particle distribution at time {m.get_current_time()}:\", val)\n", + "print(f\"Sum: {val.sum()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "2d2542c5-0603-4966-8990-464fe2d74d4a", + "metadata": {}, + "source": [ + "The particle distribution might be easier to understand if we redimensionalize the array." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5039f9e5-a42e-4efb-9988-1cb32bfe5abf", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Particle distribution at time {m.get_current_time()}\")\n", + "print(val.reshape((grid_shape)))" + ] + }, + { + "cell_type": "markdown", + "id": "6fc4974e-093b-42cf-8ea0-039a23567183", + "metadata": {}, + "source": [ + "Run the model to its end time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71d9e025-a74b-441c-b57c-288ab2554471", + "metadata": {}, + "outputs": [], + "source": [ + "while m.get_current_time() < m.get_end_time():\n", + " m.update()\n", + "print(f\"Time: {m.get_current_time()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "81a50ba7-85c3-430d-a6d7-9b1eb928331b", + "metadata": {}, + "source": [ + "## View the results" + ] + }, + { + "cell_type": "markdown", + "id": "4073b148-3bfe-47ee-a1c9-be9a8240b342", + "metadata": {}, + "source": [ + "How has the particle distribution evolved over the run time of the model?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cae80918-5418-43e8-9d9b-81f3f72657ff", + "metadata": {}, + "outputs": [], + "source": [ + "m.get_value(var_name, val)\n", + "print(f\"Temperature at time {m.get_current_time()}\")\n", + "print(val.reshape((grid_shape)))\n", + "print(f\"Sum: {val.sum()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "88c81dca-2ef9-46e4-9084-aea5d395c6cd", + "metadata": {}, + "source": [ + "Visualize the final particle distribution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b21d3df3-5979-43f8-a24c-8b176784229d", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "histogram_colorbar_plot(m._model, ax, fig)" + ] + }, + { + "cell_type": "markdown", + "id": "9ff1f0ee-3a8c-4593-8901-be875ec025f6", + "metadata": {}, + "source": [ + "Diffusion!" + ] + }, + { + "cell_type": "markdown", + "id": "7a72b990-b65f-4359-acec-d08545c2e23c", + "metadata": {}, + "source": [ + "## Finalize the model" + ] + }, + { + "cell_type": "markdown", + "id": "ae65a15a-c24d-4623-9527-31955cb1f117", + "metadata": {}, + "source": [ + "Shut down the model when we're finished." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b1578e3-c020-4fef-b691-f79ab5450fb5", + "metadata": {}, + "outputs": [], + "source": [ + "m.finalize()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}