Skip to content

NGen Tutorial

Shengting Cui edited this page Feb 27, 2024 · 63 revisions

NGen Tutorial

This Tutorial will walk through the steps to build NGen and execute several formulations on a small example catchment. We will use a terminal to download, compile, and run the NGen framework.

Building NGen

A few dependencies are required to build and run NGen, those are documented in the dependencies document. For this tutorial, the minimum required dependencies are C/C++ compiler, CMake, and Boost. Additionally, to use the Fortran BMI adapter, a Fortran Compiler is required to to build the middleware, gfortran should be sufficient.

The first step after ensuring your environment has the required dependencies is to clone the NGen repository.

git clone https://github.com/noaa-owp/ngen
cd ngen

Setting up the external components

For this tutorial, we are going to use several external components, and we need to make sure they are properly set up.

  1. First, make sure all the project submodules have been retrieved/updated.
    git submodule update --init --recursive
  • It is possible to do this for an individual submodule as well, by appending -- <submodule_path> to the above command. The submodules paths can be found in the .gitmodules file.
  1. Obtain any needed external BMI modules. Several external modules have had configurations to build shared library files added within the ngen repo. Those can be built using the following commands (note on some systems you may need to use cmake3 instead of cmake).
    cmake -B extern/cfe/cmake_build -S extern/cfe/cfe/ -DNGEN=ON
    make -C extern/cfe/cmake_build
    cmake -B extern/topmodel/cmake_build -S extern/topmodel
    make -C extern/topmodel/cmake_build
    cmake -B extern/noah-owp-modular/cmake_build -S extern/noah-owp-modular -DNGEN_IS_MAIN_PROJECT=ON
    make -C extern/noah-owp-modular/cmake_build
    cmake -B extern/evapotranspiration/cmake_build -S extern/evapotranspiration/evapotranspiration/
    make -C extern/evapotranspiration/cmake_build
    cmake -B extern/sloth/cmake_build -S extern/sloth
    make -C extern/sloth/cmake_build
  • These examples are for convenience; depending on your realization configuration, you may not need to build all or any of these.
  • What matters is that the external module location (meaning the shared library file path for C/C++/Fortran) matches the realization configuration.
  1. To use the noah-owp-modular fortran library, we need to build the iso_c_fortran_bmi middleware
    cmake -B extern/iso_c_fortran_bmi/cmake_build -S extern/iso_c_fortran_bmi
    make -C extern/iso_c_fortran_bmi/cmake_build
  • While this is necessary at the time of this writing, this step should no longer be needed once Issue #333 is resolved.

Configuring the NGen build

NGen builds are managed by the CMake build system. To create a build configuration to compile NGen, use a command like the following (assumed to be run from within the ngen repo directory):

cmake -B cmake_build -S . -DNGEN_WITH_PYTHON:BOOL=OFF -DNGEN_WITH_BMI_FORTRAN:BOOL=ON -DUDUNITS_QUIET:BOOL=ON

This configures NGen to compile without Python support, and with Fortran BMI support, which is needed for Noah-OWP respectively. Note that Python support in particular must be explicitly set to OFF to deactivate it.

If you need Python, you can set it to ON, but you may need to create a local virtual environment directory at .venv and install numpy.

Tip

-DUDUNITS_QUIET:BOOL=ON will silence any unit conversion warnings that UDUnits emits

Tip

-DNGEN_WITH_SQLITE:BOOL=ON will enable support to directly read/use geopackage hydrofabrics

Now we can compile NGen

make -j 8 -C cmake_build

Testing the NGen build

Before continuing, it might be a good idea to run the unit tests for NGen. By default, NGEN_WITH_TESTS:BOOL=ON the unit tests are built automatically, so you can do:

ls cmake_build/test

to see what unit test executable are built, the run the tests, such as.

./cmake_build/test/test_unit

and the BMI integration tests

./cmake_build/test/test_bmi_c
./cmake_build/test/test_bmi_fortran
./cmake_build/test/test_bmi_python # Only if you activated Python, though

TL;DR;

Copy and paste the following into your shell. If you are on a system with both cmake and cmake3, set an alias to make sure you use cmake3.

alias cmake=cmake3

Then run

git clone https://github.com/noaa-owp/ngen &&
cd ngen &&
git submodule update --init --recursive &&
cmake -B extern/cfe/cmake_build -S extern/cfe/cfe/ -DNGEN=ON && 
make -C extern/cfe/cmake_build && 
cmake -B extern/topmodel/cmake_build -S extern/topmodel && 
make -C extern/topmodel/cmake_build && 
cmake -B extern/noah-owp-modular/cmake_build -S extern/noah-owp-modular -DNGEN_IS_MAIN_PROJECT=ON && 
make -C extern/noah-owp-modular/cmake_build &&
cmake -B extern/evapotranspiration/cmake_build -S extern/evapotranspiration/evapotranspiration &&
make -C extern/evapotranspiration/cmake_build &&
cmake -B extern/sloth/cmake_build -S extern/sloth &&
make -C extern/sloth/cmake_build &&
cmake -B extern/iso_c_fortran_bmi/cmake_build -S extern/iso_c_fortran_bmi &&
make -C extern/iso_c_fortran_bmi/cmake_build &&
cmake -B cmake_build -S . -DNGEN_WITH_PYTHON:BOOL=ON -DNGEN_WITH_BMI_C:BOOL=ON -DNGEN_WITH_BMI_FORTRAN:BOOL=ON &&
make -j 8 -C cmake_build &&
cmake -B extern/test_bmi_c/cmake_build -S extern/test_bmi_c && 
make -C extern/test_bmi_c/cmake_build &&
cmake -B extern/test_bmi_fortran/cmake_build -S extern/test_bmi_fortran -DNGEN_IS_MAIN_PROJECT=ON && 
make -C extern/test_bmi_fortran/cmake_build &&
./cmake_build/test/test_unit && 
./cmake_build/test/test_bmi_c && 
./cmake_build/test/test_bmi_fortran

Get a quick coffee...Then you should see

[       OK ] Bmi_Fortran_Formulation_Test.determine_model_time_offset_0_c (16 ms)
[----------] 11 tests from Bmi_Fortran_Formulation_Test (283 ms total)

[----------] Global test environment tear-down
[==========] 90 tests from 2 test suites ran. (334 ms total)
[  PASSED  ] 90 tests.

Everything built correctly!

Important

For a more information on the ngen build process, see Building.

Running Formulations with NGen

Now that NGen is compiled, we can run some simple formulation tests. The input to ngen looks like

ngen <catchment_data_path> <catchment subset ids> <nexus_data_path> <nexus subset ids> <realization_config_path>

Important

For a more information on ngen usage, see Usage.

Important

For a more information on ngen's use of formulations/BMI, see Formulations and BMI.

Running CFE

CFE with PET

CFE requires a Potential EvapoTranspiration input. This input needs to be supplied from some module. We could use SLoTH to provide a fake value to CFE, or we can use the evapotranspiration BMI model to compute a real PET value. We will use the PET module built in the previous steps to supply values to CFE.

The following commands create a space to store the simulation outputs and then links the relevant directories needed for forcing and libraries.

mkdir cfe
cd cfe
ln -s ../data
ln -s ../extern

The realization file configures the relevant BMI settings required to connect to the CFE formulation. We will customize the realization file to configure CFE with PET, copy the following JSON to realization.json in your cfe directory.

Realization Configuration JSON
{
    "global": {
        "formulations": [
            {
                "name": "bmi_multi",
                "params": {
                    "model_type_name": "bmi_multi_pet_cfe",
                    "forcing_file": "",
                    "init_config": "",
                    "allow_exceed_end_time": true,
                    "main_output_variable": "Q_OUT",
                    "modules": [
			{
			    "name": "bmi_c++",
			    "params": {
                                "model_type_name": "bmi_c++_sloth",
                                "library_file": "./extern/sloth/cmake_build/libslothmodel.so",
                                "init_config": "/dev/null",
                                "allow_exceed_end_time": true,
                                "main_output_variable": "z",
                                "uses_forcing_file": false,
                                "model_params": {
				    "sloth_ice_fraction_schaake(1,double,m,node)": 0.0,
				    "sloth_ice_fraction_xinanjiang(1,double,1,node)": 0.0,
				    "sloth_smp(1,double,1,node)": 0.0
                                }
			    }
                        },
                       {
                            "name": "bmi_c",
                            "params": {
                                "model_type_name": "bmi_c_pet",
                                "library_file": "./extern/evapotranspiration/cmake_build/libpetbmi",
                                "forcing_file": "",
                                "init_config": "./data/bmi/c/pet/{{id}}_bmi_config.ini",
                                "allow_exceed_end_time": true,
                                "main_output_variable": "water_potential_evaporation_flux",
                                "registration_function": "register_bmi_pet",
                                "variables_names_map": {
                                    "water_potential_evaporation_flux": "potential_evapotranspiration"
                                },
                                "uses_forcing_file": false
                            }
                        },
                        {
                            "name": "bmi_c",
                            "params": {
                                "model_type_name": "bmi_c_cfe",
                                "library_file": "./extern/cfe/cmake_build/libcfebmi",
                                "forcing_file": "",
                                "init_config": "./data/bmi/c/cfe/{{id}}_bmi_config.ini",
                                "allow_exceed_end_time": true,
                                "main_output_variable": "Q_OUT",
                                "registration_function": "register_bmi_cfe",
                                "variables_names_map": {
                                    "water_potential_evaporation_flux": "potential_evapotranspiration",
                                    "atmosphere_air_water~vapor__relative_saturation": "SPFH_2maboveground",
                                    "land_surface_air__temperature": "TMP_2maboveground",
                                    "land_surface_wind__x_component_of_velocity": "UGRD_10maboveground",
                                    "land_surface_wind__y_component_of_velocity": "VGRD_10maboveground",
                                    "land_surface_radiation~incoming~longwave__energy_flux": "DLWRF_surface",
                                    "land_surface_radiation~incoming~shortwave__energy_flux": "DSWRF_surface",
                                    "land_surface_air__pressure": "PRES_surface",
				    "ice_fraction_schaake" : "sloth_ice_fraction_schaake",
				    "ice_fraction_xinanjiang" : "sloth_ice_fraction_xinanjiang",
				    "soil_moisture_profile" : "sloth_smp"
                                },
                                "uses_forcing_file": false
                            }
                        }
                    ],
                    "uses_forcing_file": false
                }
            }
        ],
        "forcing": {
            "file_pattern": ".*{{id}}.*..csv",
            "path": "./data/forcing/",
            "provider": "CsvPerFeature"
        }
    },
    "time": {
        "start_time": "2015-12-01 00:00:00",
        "end_time": "2015-12-30 23:00:00",
        "output_interval": 3600
    }
}

Finally, run ngen, pointing it to the hydrofabric files and selecting just cat-27 to run.

../cmake_build/ngen data/catchment_data.geojson cat-27 data/nexus_data.geojson nex-26 realization.json

Now you should see in your directory the outfiles cat-27.csv and nex-26_output.csv. The cat-27.csv file contains a table of the model's OUTPUT_VARS for each time step. nex-26_output.csv contains the nexus output, which is the modules main_output_variable (as defined in the realization configuration file). This is currently multiplied by the catchments area (meters squared) as determined from the hydrofabric.

Note

This is using a generic config file to initialize the model.

Running Topmodel

For this model run, we will repeat the basic steps above:

cd ..
mkdir topmod
cd topmod
ln -s ../data
ln -s ../extern

We need to edit/create the config file we previously used, so copy it to this directory:

cp ../cfe/realization.json ./realization.json

Now copy some test data for topmodel

cp ./extern/topmodel/topmodel/data/* data

Edit the config file realization.json, specifically the global configuration. In the bmi_multi params block, set the following variables to these values:

  • "model_type_name": "bmi_multi_pet_topmodel"
  • "main_output_variable: "Qout"

We don't need the sloth module to run topmodel, so remove the first module in the bmi_multi modules list (the sloth module). We will continue to use PET module for providing PET values to topmodel. Finally, we can edit the last bmi_multi module to load topmodel.

Set the following variables to these values:

  • "model_type_name": "bmi_c_topmodel"
  • "library_file": "./extern/topmodel/cmake_build/libtopmodelbmi.<dylib/so>"
  • "init_config": "./data/topmod.run"
  • "main_output_variable": "Qout"
  • "registration_function": "register_bmi_topmodel"

Additionally, TOPMODEL writes its own output files unless specifically told not to. If you want to only have Nextgen output, please set both the stand_alone and output flags to 0 in topmod.run and subcat.dat, respectively. More info here and here and here.

Now run the model

../cmake_build/ngen data/catchment_data.geojson cat-27 data/nexus_data.geojson nex-26 realization.json

Once again, you can check the outputs in cat-27.csv and nex-26_output.csv.

Note

Comparing the cat-*.csv output file from the previous CFE model run, you will see the different model output variables from the two models being captured by ngen and written out.

Running a heterogeneous simulation

So far, we have configured and run two different model formulations (CFE with PET and SLoTH, and Topmodel with PET). We have applied these formulations in a global configuration, but we have only simulated a single catchment in our domain. Next, we will run a formulation for the entire hydrofabric domain, then we will modify the configuration to run more than one formulation in the domain.

Running the entire domain

In our topmodel working directory, we will run ngen again, this time on the entire domain.

../cmake_build/ngen data/catchment_data.geojson all data/nexus_data.geojson all realization.json

Note

Now we are asking ngen to run "all" catchments and associated nexuses, instead of a subset of the hydrofabric.

Now inspecting the outfiles created, we will see cat-27.csv and nex-26_output.csv as before, as well as cat-52.csv, cat-67.csv, nex-34_output.csv, and nex-68_output.csv.

Running Noah-OWP-Modular on a specific catchment

Now we will re-configure the realization to run a heterogeneous model where cat-27 runs a unique formulation.

Setup a space to run the model:

cd ..
mkdir noahowp
cd noahowp
ln -s ../data
ln -s ../extern

Copy the previous realization config to the directory:

cp ../topmod/realization.json ./realization.json

Now copy the namelist (model options) for Noah-OWP-Modular:

cp extern/noah-owp-modular/noah-owp-modular/run/namelist.input .

Open the newly copied namelist.input file and modify the parameter_dir to include the path for the parameter tables. For this example, that is ./extern/noah-owp-modular/noah-owp-modular/parameters/.

Edit the config file realization.json, we will specify an override formulation for cat-27. We need to create the "catchments": {} key, and add our catchment specific formulations. Add a comma after the "time" object and then add the following:

 "catchments":{
        "cat-27":{
            "formulations":[
                {
                    "name": "bmi_fortran",
                    "params": {
                        "model_type_name": "bmi_fortran_noahowp",
                        "library_file": "./extern/noah-owp-modular/cmake_build/libsurfacebmi",
                        "registration_function": "register_bmi",
                        "init_config": "namelist.input",
                        "main_output_variable": "QINSUR",
                        "forcing_file": "",
                        "allow_exceed_end_time": true,
                        "uses_forcing_file": false,
                        "variables_names_map" : {
                            "PRCPNONC" : "atmosphere_water__liquid_equivalent_precipitation_rate",
                            "Q2" : "atmosphere_air_water~vapor__relative_saturation",
                            "SFCTMP" : "land_surface_air__temperature",
                            "UU" : "land_surface_wind__x_component_of_velocity",
                            "VV" : "land_surface_wind__y_component_of_velocity",
                            "LWDN" : "land_surface_radiation~incoming~longwave__energy_flux",
                            "SOLDN" : "land_surface_radiation~incoming~shortwave__energy_flux",
                            "SFCPRS" : "land_surface_air__pressure"
                        }
                    }
               }
            ],
            "forcing": {
                "file_pattern": ".*{{id}}.*..csv",
                "path": "./data/forcing/",
                "provider": "CsvPerFeature"
            }   
        }
    }       

Notice we also added a catchment specific forcing key to indicate the forcing data to use for this catchment. We used the same file pattern and location as before, but we could also choose to point this catchment to a new source of forcing data if we wanted to.

Now run the model

../cmake_build/ngen data/catchment_data.geojson all data/nexus_data.geojson all realization.json

Check out the outputs produced, and you will see that cat-27.csv contains Noah-OWP-Modular output variables, whereas the other catchments contain topmodel outputs! We have successfully run a heterogenous model over our three example catchments!