From 4a0d19ced2f0e8a14bae38875dea012cbf10a767 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Tue, 6 Jun 2023 23:28:36 +0100 Subject: [PATCH 1/4] Adding Fortran example --- .vscode/settings.json | 3 + docs/source/babel_heatf.toml | 28 ++ docs/source/environment-fortran.yml | 12 + docs/source/example-fortran.rst | 426 +++++++++++++++++++ docs/source/examples/fortran/heat.txt | 1 + docs/source/examples/fortran/info.yaml | 8 + docs/source/examples/fortran/parameters.yaml | 42 ++ docs/source/examples/fortran/run.yaml | 1 + docs/source/examples/pymt_heatc_ex.py | 0 docs/source/index.rst | 1 + 10 files changed, 522 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 docs/source/babel_heatf.toml create mode 100644 docs/source/environment-fortran.yml create mode 100644 docs/source/example-fortran.rst create mode 100644 docs/source/examples/fortran/heat.txt create mode 100644 docs/source/examples/fortran/info.yaml create mode 100644 docs/source/examples/fortran/parameters.yaml create mode 100644 docs/source/examples/fortran/run.yaml mode change 100644 => 100755 docs/source/examples/pymt_heatc_ex.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..cad7657d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.configureOnOpen": false +} \ No newline at end of file diff --git a/docs/source/babel_heatf.toml b/docs/source/babel_heatf.toml new file mode 100644 index 00000000..9332984d --- /dev/null +++ b/docs/source/babel_heatf.toml @@ -0,0 +1,28 @@ +[library.HeatModel] +language = "fortran" +library = "bmiheatf" +header = "" +entry_point = "bmi_heat" + +[build] +undef_macros = [] +define_macros = [] +libraries = [] +library_dirs = [] +include_dirs = [] +extra_compile_args = [] + +[package] +name = "pymt_heatf" +requirements = [""] + +[info] +github_username = "pymt-lab" +package_author = "csdms" +package_author_email = "csdms@colorado.edu" +package_license = "MIT License" +summary = "PyMT plugin for the Fortran heat model" + +[ci] +python_version = ["3.9"] +os = ["linux", "mac", "windows"] \ No newline at end of file diff --git a/docs/source/environment-fortran.yml b/docs/source/environment-fortran.yml new file mode 100644 index 00000000..6bfd6118 --- /dev/null +++ b/docs/source/environment-fortran.yml @@ -0,0 +1,12 @@ +# A conda environment file for the babelizer Fortran example +name: wrap +channels: + - conda-forge +dependencies: + - python=3 + - make + - cmake + - pkg-config + - fortran-compiler + - bmi-fortran + - babelizer diff --git a/docs/source/example-fortran.rst b/docs/source/example-fortran.rst new file mode 100644 index 00000000..bb08ef42 --- /dev/null +++ b/docs/source/example-fortran.rst @@ -0,0 +1,426 @@ +Example: Wrapping a Fortran model +================================= + +In this example, we'll use the *babelizer* +to wrap the *heat* model from the `bmi-example-fortran`_ repository, +allowing it to be run in Python. +The model and its BMI are written in Fortran. +To simplify package management in the example, +we'll use :term:`conda`. +We'll also use :term:`git` to obtain the model source code. + +This is a somewhat long example. +To break it up, +here are the steps we'll take: + +#. Create a :term:`conda environment` that includes software to compile the + model and wrap it with the *babelizer* +#. Clone the `bmi-example-fortran`_ repository from GitHub and build the + *heat* model from source +#. Create a *babelizer* input file describing the *heat* model +#. Run the *babelizer* to generate Python bindings, then build the bindings +#. Show the *heat* model running in Python through *pymt* + +Before we begin, +create a directory to hold our work: + +.. code:: bash + + $ mkdir build && cd build + +This directory is a starting point; +we'll make new directories under it as we proceed through the example. +In the end, +the first level of the directory structure under ``build`` should look like this: + +.. code:: + + . + ├── babel_heatf.toml + ├── bmi-example-fortran + │ └── ... + ├── environment-fortran.yml + ├── pymt_heatf + │ └── ... + └── test + └── ... + +Set up a conda environment +-------------------------- + +Start by setting up a :term:`conda environment` that includes the *babelizer*, +as well as a toolchain to build and install the model. +The necessary packages are listed in the conda environment file +:download:`environment-fortran.yml`: + +.. include:: environment-fortran.yml + :literal: + +:download:`Download ` this file +and create the new environment with: + +.. code:: bash + + $ conda env create --file=environment-fortran.yml + +When this command completes, +activate the environment +(on Linux and macOS, you may have to use ``source`` instead of ``conda``): + +.. code:: bash + + $ conda activate wrap + +The *wrap* environment now contains all the dependencies needed +to build, install, and wrap the *heat* model. + + +Build the *heat* model from source +---------------------------------- + +Clone the `bmi-example-fortran`_ repository from GitHub: + +.. code:: bash + + $ git clone https://github.com/csdms/bmi-example-fortran + +There are general `instructions`_ in the repository for building and installing +this package on Linux, macOS, and Windows. +We'll augment those instructions +with the note that we're installing into the *wrap* conda environment, +so the ``CONDA_PREFIX`` environment variable +should be used to specify the install path. + +Note that if you build the model with the +`Fortran Package Manager `_ +(fpm), you will end up with a static library (`.a` on Unix, `.lib` on +Windows) instead of the dynamic library (`.so` on Unix, `.dll` on +Windows) that the CMake build creates. We are aware of +issues linking to the compiler runtime libraries from this static +library, and for this reason we recommend using the CMake build +routine, as detailed below. + + +Linux and macOS +............... + +On Linux and macOS, +use these commands to build and install the *heat* model: + +.. code:: bash + + $ cd bmi-example-fortran + $ mkdir _build && cd _build + $ cmake .. -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX + $ make install + +Verify the install by testing for the existence of the module +file of the library containing the compiled *heat* model: + +.. code:: bash + + $ test -f $CONDA_PREFIX/include/bmiheatf.mod ; echo $? + +A return of zero indicates success. + +Windows +....... + +Building the *heat* model on Windows requires either: + +* A Unix-like system, such as `Cygwin `_ or + `Windows Subsystem for Linux `_, + in which case you can follow the above Linux and macOS instructions. +* Microsoft Visual Studio 2017 or later, or Microsoft Build Tools for + Visual Studio 2017 or later, in which case the following instructions should be followed. + +Open the `Developer Command Prompt`_ and run: + +.. code:: + + > cd bmi-example-fortran + > mkdir _build + > cd _build + > cmake .. ^ + -G "NMake Makefiles" ^ + -DCMAKE_INSTALL_PREFIX=%CONDA_PREFIX% ^ + -DCMAKE_BUILD_TYPE=Release + > cmake --build . --target install --config Release + +Verify the install by testing for the existence of the module +file of the library containing the compiled *heat* model: + +.. code:: + + > if exist %CONDA_PREFIX%\include\bmiheatf.mod echo File exists + +Note that on Windows systems, the conda package we specified +called `fortran-compiler` installs a fairly old version of the Flang +compiler, and there are few options for more modern compilers +available via conda (as opposed to for Unix, where modern versions +of `GFortran are available `_). +This is fine for the example *heat* model, but code bases leveraging +newer features of Fortran may need a more modern compiler. In this +case, it might be necessary to install a Fortran compiler separately +to conda, for example using the binaries provided by +`equation.com `_. +The `BMI bindings `_ (installed +here via conda) should be compiled with the same compiler as the model +that uses them, to avoid incompatibility issues, and so if you choose +a different compiler than provided by `fortran-compiler`, you will +likely have to compile the BMI bindings with this compiler as well. + + +Create the *babelizer* input file +--------------------------------- + +The *babelizer* input file provides information to the *babelizer* +about the model to be wrapped. +The input file is created with the ``babelize generate`` subcommand. + +Return to our initial ``build`` directory and call ``babelize generate`` with: + +.. code:: bash + + $ cd ../.. + $ babelize generate \ + --package=pymt_heatf \ + --summary="PyMT plugin for the Fortran heat model" \ + --language=fortran \ + --library=bmiheatf \ + --entry-point=bmi_heat \ + --name=HeatModel \ + --requirement="" > babel_heatf.toml + +In this call, +the *babelizer* will also fill in default values; +e.g., author name, author email, GitHub username, and license. + +The resulting file, :download:`babel_heatf.toml`, +will look something like this: + +.. include:: babel_heatf.toml + :literal: + +For more information on the entries and sections of the *babelizer* input file, +see `Input file <./readme.html#input-file>`_. + + +Wrap the model with the *babelizer* +----------------------------------- + +Generate Python bindings for the model with the ``babelize init`` subcommand: + +.. code:: bash + + $ babelize init babel_heatf.toml + +The results are placed in a new directory, ``pymt_heatf``, +under the current directory. + +.. code:: bash + + $ ls -aF pymt_heatf + ./ .gitignore recipe/ + ../ LICENSE requirements-build.txt + babel.toml Makefile requirements-library.txt + CHANGES.rst MANIFEST.in requirements-testing.txt + CREDITS.rst meta/ requirements.txt + docs/ pymt_heatf/ setup.cfg + .git/ pyproject.toml setup.py + .github/ README.rst + +Before we can build the Python bindings, +we must ensure that the dependencies required by the toolchain, +as well as any required by the model, +as specified in the *babelizer* input file (none in this case), +are satisfied. + +Change to the ``pymt_heatf`` directory and install dependencies +into the conda environment: + +.. code:: bash + + $ cd pymt_heatf + $ conda install -c conda-forge \ + --file=requirements-build.txt \ + --file=requirements-testing.txt \ + --file=requirements-library.txt \ + --file=requirements.txt + +Now build the Python bindings with: + +.. code:: bash + + $ make install + +This command sets off a long list of messages, +at the end of which you'll hopefully see: + +.. code:: bash + + Successfully installed pymt-heatf-0.1 + +Internally, this uses `pip` to install the Python +package in editable mode. + +Pause a moment to see what we've done. +Change back to the initial ``build`` directory, +make a new ``test`` directory, +and change to it: + +.. code:: bash + + $ cd .. + $ mkdir test && cd test + +Start a Python session (e.g. run ``python``) and try the following commands: + +.. code:: python + + >>> from pymt_heatf import HeatModel + >>> m = HeatModel() + >>> m.get_component_name() + 'The 2D Heat Equation' + +We've imported the *heat* model, +written in Fortran, +into Python! +Exit the Python session (e.g. type ``exit()``) + +At this point, +it's a good idea to run the *bmi-tester* (`GitHub repo `_) +over the model. +The *bmi-tester* exercises each BMI method exposed through Python, +ensuring it works correctly. +However, before running the *bmi-tester*, +one last piece of information is needed. +Like all models equipped with a BMI, +*heat* uses a :term:`configuration file` to specify initial parameter values. +Create a configuration file for *heat* at the command line with: + +.. code:: bash + + $ echo "1.5, 8.0, 6, 5" > config.txt + +or download the file :download:`config.txt ` +(making sure to place it in the ``test`` directory). + +Run the *bmi-tester*: + +.. code:: bash + + $ bmi-test pymt_heatf:HeatModel --config-file=config.txt --root-dir=. -vvv + +This command sets off a long list of messages, +ending with + +.. code:: bash + + 🎉 All tests passed! + +if everything has been built correctly. + + +Add metadata to make a *pymt* component +--------------------------------------- + +The final step in wrapping the *heat* model +is to add metadata used by the `Python Modeling Tool`_, *pymt*. +CSDMS develops a set of standards, +the `CSDMS Model Metadata`_, +that provides a detailed and formalized description of a model. +The metadata allow *heat* to be run and be :term:`coupled ` +with other models that expose a BMI and have been similarly wrapped +with the *babelizer*. + +Recall the *babelizer* outputs the wrapped *heat* model +to the directory ``pymt_heatf``. +Under this directory, +the *babelizer* created a directory for *heat* model metadata, +``meta/HeatModel``. +Change back to the ``pymt_heatf`` directory +and view the current metadata: + +.. code:: bash + + $ cd ../pymt_heatf + $ ls meta/HeatModel/ + api.yaml + +The file ``api.yaml`` is automatically generated by the *babelizer*. +To complete the description of the *heat* model, +other metadata files are needed, including: + +* :download:`info.yaml ` +* :download:`run.yaml ` +* a :download:`templated model configuration file ` +* :download:`parameters.yaml ` + +`Descriptions`_ of these files and their roles +are given in the CSDMS Model Metadata repository. +Download each of the files using the links in the list above +and place them in the ``pymt_heatf/meta/HeatModel`` directory +alongside ``api.yaml``. + +Next, install *pymt*: + +.. code:: bash + + $ conda install -c conda-forge pymt + +Then start a Python session and show that the *heat* model +can be called through *pymt*: + +.. code:: python + + >>> from pymt.models import HeatModel + >>> m = HeatModel() + >>> m.name + 'The 2D Heat Equation' + +A longer example, +:download:`pymt_heatc_ex.py `, +is included in the documentation. +For easy viewing, it's reproduced here verbatim: + +.. include:: examples/pymt_heatc_ex.py + :literal: + +:download:`Download ` this Python script, +make sure we're still in the `test` directory we just created, +then run it with: + +.. code:: bash + + $ python pymt_heatc_ex.py + +Note that here we are actually running the Python script that +was developed for the :doc:`C example `, not Fortran. +That is one of the powerful things about wrapping your +BMI-enabled model and accessing it via PyMT - it provides a +standardised interface, regardless of the underlying model +and the language it was written in. + + +Summary +------- + +Using the *babelizer*, we wrapped the *heat* model, which is written in Fortran. +It can now be called as a *pymt* component in Python. + +The steps for wrapping a model with the *babelizer* outlined in this example +can also be applied to models written in C (:doc:`see the example `) +and C++. + + +.. + Links + +.. _bmi-example-fortran: https://github.com/csdms/bmi-example-fortran +.. _instructions: https://github.com/csdms/bmi-example-c/blob/master/README.md +.. _Developer Command Prompt: https://docs.microsoft.com/en-us/dotnet/framework/tools/developer-command-prompt-for-vs +.. _bmi-tester: https://github.com/csdms/bmi-tester +.. _Python Modeling Tool: https://pymt.readthedocs.io +.. _CSDMS Model Metadata: https://github.com/csdms/model_metadata +.. _Descriptions: https://github.com/csdms/model_metadata/blob/develop/README.rst diff --git a/docs/source/examples/fortran/heat.txt b/docs/source/examples/fortran/heat.txt new file mode 100644 index 00000000..7ea565ae --- /dev/null +++ b/docs/source/examples/fortran/heat.txt @@ -0,0 +1 @@ +{{thermal_diffusivity}}, {{run_duration}}, {{number_of_columns}}, {{number_of_rows}} diff --git a/docs/source/examples/fortran/info.yaml b/docs/source/examples/fortran/info.yaml new file mode 100644 index 00000000..fae21955 --- /dev/null +++ b/docs/source/examples/fortran/info.yaml @@ -0,0 +1,8 @@ +summary: | + This model solves the two-dimensional heat equation on a uniform + rectilinear grid. +url: https://github.com/csdms/bmi-example-fortran +author: CSDMS +email: csdms@colorado.edu +version: 0.2 +license: MIT diff --git a/docs/source/examples/fortran/parameters.yaml b/docs/source/examples/fortran/parameters.yaml new file mode 100644 index 00000000..3c1c5f2b --- /dev/null +++ b/docs/source/examples/fortran/parameters.yaml @@ -0,0 +1,42 @@ +run_duration: + description: Simulation run time + value: + type: float + default: 1.0 + units: s + range: + min: 0.0 + max: 1000000.0 + +thermal_diffusivity: + name: Thermal diffusivity + description: Thermal diffusivity + value: + type: float + default: 1.0 + range: + min: 0.0 + max: 10.0 + units: 'm2 s-1' + +number_of_rows: + name: Number of rows + description: Number of grid rows + value: + type: int + default: 10 + range: + min: 0 + max: 10000 + units: '1' + +number_of_columns: + name: Number of columns + description: Number of grid columns + value: + type: int + default: 10 + range: + min: 0 + max: 10000 + units: '1' diff --git a/docs/source/examples/fortran/run.yaml b/docs/source/examples/fortran/run.yaml new file mode 100644 index 00000000..3b0457ff --- /dev/null +++ b/docs/source/examples/fortran/run.yaml @@ -0,0 +1 @@ +config_file: heat.txt diff --git a/docs/source/examples/pymt_heatc_ex.py b/docs/source/examples/pymt_heatc_ex.py old mode 100644 new mode 100755 diff --git a/docs/source/index.rst b/docs/source/index.rst index 758e9e33..dedebede 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -41,6 +41,7 @@ User Guide readme cli example + example-fortran glossary From 7f49316c187308431cf2fd4d66849fe409490fd5 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Mon, 4 Mar 2024 16:24:52 -0700 Subject: [PATCH 2/4] add news fragment --- news/86.doc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 news/86.doc diff --git a/news/86.doc b/news/86.doc new file mode 100644 index 00000000..b859ea4f --- /dev/null +++ b/news/86.doc @@ -0,0 +1,3 @@ + +Added an example to the docs that shows how to use the *babelizer* to wrap +a Fortran model. From 3a35af46ef31c15dcf983753db5099e19567bbb9 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Mon, 4 Mar 2024 16:26:02 -0700 Subject: [PATCH 3/4] remove vscode files --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index cad7657d..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cmake.configureOnOpen": false -} \ No newline at end of file From b80f97b192f77aae58579b4869f0df5c011e6d17 Mon Sep 17 00:00:00 2001 From: mcflugen Date: Mon, 4 Mar 2024 16:26:18 -0700 Subject: [PATCH 4/4] remove lint --- docs/source/babel_heatf.toml | 2 +- docs/source/example-fortran.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/babel_heatf.toml b/docs/source/babel_heatf.toml index 9332984d..678ee596 100644 --- a/docs/source/babel_heatf.toml +++ b/docs/source/babel_heatf.toml @@ -25,4 +25,4 @@ summary = "PyMT plugin for the Fortran heat model" [ci] python_version = ["3.9"] -os = ["linux", "mac", "windows"] \ No newline at end of file +os = ["linux", "mac", "windows"] diff --git a/docs/source/example-fortran.rst b/docs/source/example-fortran.rst index bb08ef42..536ce84e 100644 --- a/docs/source/example-fortran.rst +++ b/docs/source/example-fortran.rst @@ -128,7 +128,7 @@ Windows Building the *heat* model on Windows requires either: -* A Unix-like system, such as `Cygwin `_ or +* A Unix-like system, such as `Cygwin `_ or `Windows Subsystem for Linux `_, in which case you can follow the above Linux and macOS instructions. * Microsoft Visual Studio 2017 or later, or Microsoft Build Tools for @@ -167,7 +167,7 @@ to conda, for example using the binaries provided by The `BMI bindings `_ (installed here via conda) should be compiled with the same compiler as the model that uses them, to avoid incompatibility issues, and so if you choose -a different compiler than provided by `fortran-compiler`, you will +a different compiler than provided by `fortran-compiler`, you will likely have to compile the BMI bindings with this compiler as well.