In this section we'll look at code for an example NUOPC Model cap. The example shows the basic structure of a NUOPC Model cap for a fictitious atmosphere model called ATM. It is slightly simpler than a “real” cap, but has enough detail to show the basic coding structures. Each section of the example cap code will be broken down and described separately.
Finding More NUOPC Code Examples
In addition to the example code in this section, the
NUOPC Prototypes
repository contains many small example applications that are helpful
for understanding the architecture of NUOPC applications and showing
example uses of the NUOPC API. These example applications can be
compiled and executed on your system.
A good starting point is the SingleModelProto application, which includes a single Model with a Driver and the AtmOcnProto application which includes two Models, a Connector, and a Driver.
module ATM !----------------------------------------------------------------------------- ! Basic NUOPC Model cap for ATM component (a fictitious atmosphere model). !----------------------------------------------------------------------------- use ESMF use NUOPC use NUOPC_Model, & modelSS => SetServices implicit none private public :: SetServices !----------------------------------------------------------------------------- contains !-----------------------------------------------------------------------------
In the example code, the call to NUOPC_CompDerive() indicates that this component derives from (and specializes) the generic NUOPC_Model component. In other words, this is a NUOPC_Model component customized for a specific model.
The calls to NUOPC_CompSpecialize() register subroutines that are implemented in the cap. The specLabel argument specifies NUOPC-defined specialization labels. NUOPC defines explicitly what happens during each phase of the initialization and these labels uniquely define any specialization that might be supplied by the user. For example, label_Advertise is responsible for advertising field in the import- and exportState of the component. The NUOPC_CompSpecialize() also takes the specRoutine argument to indicate what routine provides the actual specialization. This subroutine appears later on in the cap and the name of the registered subroutine is entirely up to you.
The same specialization approach is used to specialize the generic Run method. Here label_Advance is specialized by subroutine Advance. The Advance specialization point is called by NUOPC whenever it needs your model to take a single timestep forward. Basically, this means you'll need to add a call inside the specialization subroutine to your model's timestepping subroutine.
subroutine SetServices(model, rc) type(ESMF_GridComp) :: model integer, intent(out) :: rc rc = ESMF_SUCCESS ! the NUOPC model component will register the generic methods call NUOPC_CompDerive(model, modelSS, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! specialize model call NUOPC_CompSpecialize(model, specLabel=label_Advertise, & specRoutine=Advertise, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out call NUOPC_CompSpecialize(model, specLabel=label_RealizeProvided, & specRoutine=Realize, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out call NUOPC_CompSpecialize(model, specLabel=label_Advance, & specRoutine=Advance, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out end subroutine
For now you should notice a few things:
The purpose of this phase is for your model to advertise its import and export fields. This means that your model announces which model variables it is capable of exporting (e.g., an atmosphere might export air pressure at sea level) and which model variables it requires (e.g., an atmosphere might require sea surface temperature as a boundary condition). The reason there is an explicit advertise phase is because NUOPC dynamically matches fields among all the models participating in a coupled simulation during runtime. So, we need to collect the list of possible input and output fields from all the models during their initialization.
As shown in the code below, to advertise a field you call NUOPC_Advertise with the following parameters:
The example code below advertises one import field with the standard name "sea_surface_temperature", and two export fields with standard names "air_pressure_at_sea_level" and "surface_net_downward_shortwave_flux".
Advertising a Field does NOT allocate memory
Note that NUOPC does not allocate memory for fields during the
advertise phase or when NUOPC_Advertise is called.
Instead, this is simply a way for models to communicate the
standard names of fields. During a later phase, only those fields that
are connected (e.g., a field exported from one model that is
imported by another) need to have memory allocated.
Also, since ESMF will accept pointers to pre-allocated memory, it is usually not
necessary to change how memory is allocated for your model's variables.
!----------------------------------------------------------------------------- subroutine Advertise(model, rc) type(ESMF_GridComp) :: model integer, intent(out) :: rc ! local variables type(ESMF_State) :: importState, exportState rc = ESMF_SUCCESS ! query for importState and exportState call NUOPC_ModelGet(model, importState=importState, & exportState=exportState, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! importable field: sea_surface_temperature call NUOPC_Advertise(importState, & StandardName="sea_surface_temperature", name="sst", & TransferOfferGeomObject="will provide", rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! exportable field: air_pressure_at_sea_level call NUOPC_Advertise(exportState, & StandardName="air_pressure_at_sea_level", name="pmsl", & TransferOfferGeomObject="will provide", rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! exportable field: surface_net_downward_shortwave_flux call NUOPC_Advertise(exportState, & StandardName="surface_net_downward_shortwave_flux", name="rsns", & TransferOfferGeomObject="will provide", rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out end subroutine
The following code fragment shows the Realize subroutine, which specializes label_RealizeProvided. During this phase, fields that were previously advertised should now be realized. Realizing a field means that an ESMF_Field object is created and it is added to the appropriate ESMF_State, either import or export.
In order to create an ESMF_Field, you'll first need to create one of the ESMF geometric types, ESMF_Grid, ESMF_Mesh, or ESMF_LocStream. For 2D and 3D logically rectangular grids (such as a lat-lon grid), the typical choice is ESMF_Grid. For unstructured grids, use an ESMF_Mesh.
Describing your model's grid (physical discretization) in the ESMF representation is one of the most important parts of creating a NUOPC cap. The ESMF geometric types are described in detail in the ESMF Reference Manual:
For the sake a simplicity, a 10x100 Cartesian grid is created in the code below and assigned to the variable gridIn.
An ESMF_Field is created by by passing in the field name (should be the same as advertised), the grid, and the data type of the field to ESMF_FieldCreate.
Fields are put into import or export States by calling NUOPC_Realize. The example code realizes three fields in total, one import and two export, and all three share the same grid.
subroutine Realize(model, rc) type(ESMF_GridComp) :: model integer, intent(out) :: rc ! local variables type(ESMF_State) :: importState, exportState type(ESMF_Field) :: field type(ESMF_Grid) :: gridIn type(ESMF_Grid) :: gridOut rc = ESMF_SUCCESS ! query for importState and exportState call NUOPC_ModelGet(model, importState=importState, & exportState=exportState, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! create a Grid object for Fields gridIn = ESMF_GridCreateNoPeriDimUfrm(maxIndex=(/10, 100/), & minCornerCoord=(/10._ESMF_KIND_R8, 20._ESMF_KIND_R8/), & maxCornerCoord=(/100._ESMF_KIND_R8, 200._ESMF_KIND_R8/), & coordSys=ESMF_COORDSYS_CART, staggerLocList=(/ESMF_STAGGERLOC_CENTER/), & rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out gridOut = gridIn ! for now out same as in ! importable field: sea_surface_temperature field = ESMF_FieldCreate(name="sst", grid=gridIn, & typekind=ESMF_TYPEKIND_R8, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out call NUOPC_Realize(importState, field=field, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! exportable field: air_pressure_at_sea_level field = ESMF_FieldCreate(name="pmsl", grid=gridOut, & typekind=ESMF_TYPEKIND_R8, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out call NUOPC_Realize(exportState, field=field, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! exportable field: surface_net_downward_shortwave_flux field = ESMF_FieldCreate(name="rsns", grid=gridOut, & typekind=ESMF_TYPEKIND_R8, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out call NUOPC_Realize(exportState, field=field, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out end subroutine
As described in the section 4.2, the subroutine Advance (shown below) has been registered to the specialization point with the label model_label_Advance in the SetServices subroutine. This specialization point subroutine is called within the generic NUOPC_Model run phase in order to request that your model take a timestep forward. The code to do this is model dependent, so it does not appear in the subroutine below.
Each NUOPC component maintains its own clock (an ESMF_Clock object). The clock is used here to indicate the current model time and the timestep size. When the subroutine finishes, your model should be moved ahead in time from the current time by one timestep. NUOPC will automatically advance the clock for you, so there is no explicit call to do that here.
Since there is no actual model for us to advance in this example, the code below simply prints the current time and stop time (current time + timestep) to standard out.
With respect to specialization point subroutines in general, note that:
subroutine Advance(model, rc) type(ESMF_GridComp) :: model integer, intent(out) :: rc ! local variables type(ESMF_Clock) :: clock type(ESMF_State) :: importState, exportState rc = ESMF_SUCCESS ! query the Component for its clock, importState and exportState call NUOPC_ModelGet(model, modelClock=clock, importState=importState, & exportState=exportState, rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out ! HERE THE MODEL ADVANCES: currTime -> currTime + timeStep ! Because of the way that the internal Clock was set by default, ! its timeStep is equal to the parent timeStep. As a consequence the ! currTime + timeStep is equal to the stopTime of the internal Clock ! for this call of the Advance() routine. call ESMF_ClockPrint(clock, options="currTime", & preString="------>Advancing ATM from: ", rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out call ESMF_ClockPrint(clock, options="stopTime", & preString="--------------------------------> to: ", rc=rc) if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, & line=__LINE__, & file=__FILE__)) & return ! bail out end subroutine end module
esmf_support@ucar.edu