ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_NoahMPVegetationTable.H
Go to the documentation of this file.
1 #ifndef ERF_NOAHMP_VEGETATION_TABLE_H_
2 #define ERF_NOAHMP_VEGETATION_TABLE_H_
3 
4 /**
5  * @file ERF_NoahMPVegetationTable.H
6  * @brief Noah-MP's canopy-resistance parameters, roughness length and monthly leaf area index by land-use
7  * category, for the two-stream surface energy balance (erf.radiation.seb_vegetation_type).
8  *
9  * The 20 categories of the modified IGBP MODIS land-use dataset and their values in
10  * &noahmp_modis_parameters of Submodules/Noah-MP/parameters/NoahmpTable.TBL -- the table
11  * Noah-MP itself reads -- copied here so the balance can use them in builds without
12  * Noah-MP. The CTest NoahMPSoilTable_MatchesSubmodule (Tests/check_noahmp_soil_table.py)
13  * fails if the two drift apart.
14  */
15 
16 #include <AMReX_REAL.H>
17 
18 #include <cstdio>
19 #include <string>
20 
21 /** One land-use category's parameters, named as in NoahmpTable.TBL. */
23 {
24  amrex::Real rs_min; ///< RS minimum stomatal resistance [s/m]
25  amrex::Real rgl; ///< RGL radiation stress parameter [W/m^2]
26  amrex::Real hs; ///< HS vapour pressure deficit parameter [1/(kg/kg)]
27  amrex::Real t_opt; ///< TOPT optimum transpiration air temperature [K]
28  amrex::Real rs_max; ///< RSMAX maximum stomatal resistance [s/m]
29  amrex::Real z0; ///< Z0MVT momentum roughness length of the vegetation [m]
30  amrex::Real lai[12]; ///< LAI_JAN ... LAI_DEC monthly leaf area index [m^2/m^2]
31 };
32 
33 /** Number of MODIS land-use categories; categories are numbered from 1, as in Noah-MP. */
34 inline constexpr int noahmp_num_vegetation_categories = 20;
35 
36 /**
37  * @brief The parameters of MODIS land-use category @p category (1-20), as Noah-MP reads them.
38  * @return nullptr for a category outside 1-20
39  */
40 [[nodiscard]] inline const NoahMPVegetationParams*
41 noahmp_vegetation_params (int category) noexcept
42 {
43  // {RS, RGL, HS, TOPT, RSMAX, Z0MVT, {LAI_JAN, ..., LAI_DEC}}
45  {amrex::Real(125.0), amrex::Real(30.0), amrex::Real(47.35), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(1.09), // 1 Evergreen Needleleaf Forest
46  {amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0), amrex::Real(4.0)}},
47  {amrex::Real(150.0), amrex::Real(30.0), amrex::Real(41.69), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(1.10), // 2 Evergreen Broadleaf Forest
48  {amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5), amrex::Real(4.5)}},
49  {amrex::Real(150.0), amrex::Real(30.0), amrex::Real(47.35), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.85), // 3 Deciduous Needleleaf Forest
50  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.6), amrex::Real(1.2), amrex::Real(2.0), amrex::Real(2.6), amrex::Real(1.7), amrex::Real(1.0), amrex::Real(0.5), amrex::Real(0.2), amrex::Real(0.0)}},
51  {amrex::Real(100.0), amrex::Real(30.0), amrex::Real(54.53), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.80), // 4 Deciduous Broadleaf Forest
52  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.3), amrex::Real(1.2), amrex::Real(3.0), amrex::Real(4.7), amrex::Real(4.5), amrex::Real(3.4), amrex::Real(1.2), amrex::Real(0.3), amrex::Real(0.0), amrex::Real(0.0)}},
53  {amrex::Real(125.0), amrex::Real(30.0), amrex::Real(51.93), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.80), // 5 Mixed Forests
54  {amrex::Real(2.0), amrex::Real(2.0), amrex::Real(2.2), amrex::Real(2.6), amrex::Real(3.5), amrex::Real(4.3), amrex::Real(4.3), amrex::Real(3.7), amrex::Real(2.6), amrex::Real(2.2), amrex::Real(2.0), amrex::Real(2.0)}},
55  {amrex::Real(300.0), amrex::Real(100.0), amrex::Real(42.00), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.20), // 6 Closed Shrublands
56  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.3), amrex::Real(0.9), amrex::Real(2.2), amrex::Real(3.5), amrex::Real(3.5), amrex::Real(2.5), amrex::Real(0.9), amrex::Real(0.3), amrex::Real(0.0), amrex::Real(0.0)}},
57  {amrex::Real(170.0), amrex::Real(100.0), amrex::Real(39.18), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.06), // 7 Open Shrublands
58  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.2), amrex::Real(0.6), amrex::Real(1.5), amrex::Real(2.3), amrex::Real(2.3), amrex::Real(1.7), amrex::Real(0.6), amrex::Real(0.2), amrex::Real(0.0), amrex::Real(0.0)}},
59  {amrex::Real(300.0), amrex::Real(100.0), amrex::Real(42.00), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.60), // 8 Woody Savannas
60  {amrex::Real(0.2), amrex::Real(0.2), amrex::Real(0.4), amrex::Real(1.0), amrex::Real(2.4), amrex::Real(4.1), amrex::Real(4.1), amrex::Real(2.7), amrex::Real(1.0), amrex::Real(0.4), amrex::Real(0.2), amrex::Real(0.2)}},
61  {amrex::Real(70.0), amrex::Real(65.0), amrex::Real(54.53), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.50), // 9 Savannas
62  {amrex::Real(0.3), amrex::Real(0.3), amrex::Real(0.5), amrex::Real(0.8), amrex::Real(1.8), amrex::Real(3.6), amrex::Real(3.8), amrex::Real(2.1), amrex::Real(0.9), amrex::Real(0.5), amrex::Real(0.3), amrex::Real(0.3)}},
63  {amrex::Real(40.0), amrex::Real(100.0), amrex::Real(36.35), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.12), // 10 Grasslands
64  {amrex::Real(0.4), amrex::Real(0.5), amrex::Real(0.6), amrex::Real(0.7), amrex::Real(1.2), amrex::Real(3.0), amrex::Real(3.5), amrex::Real(1.5), amrex::Real(0.7), amrex::Real(0.6), amrex::Real(0.5), amrex::Real(0.4)}},
65  {amrex::Real(70.0), amrex::Real(65.0), amrex::Real(55.97), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.30), // 11 Permanent wetlands
66  {amrex::Real(0.2), amrex::Real(0.3), amrex::Real(0.3), amrex::Real(0.5), amrex::Real(1.5), amrex::Real(2.9), amrex::Real(3.5), amrex::Real(2.7), amrex::Real(1.2), amrex::Real(0.3), amrex::Real(0.3), amrex::Real(0.2)}},
67  {amrex::Real(40.0), amrex::Real(100.0), amrex::Real(36.25), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.15), // 12 Croplands
68  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(1.0), amrex::Real(2.0), amrex::Real(3.0), amrex::Real(3.0), amrex::Real(1.5), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0)}},
69  {amrex::Real(200.0), amrex::Real(999.0), amrex::Real(999.0), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(1.00), // 13 Urban and Built-Up
70  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0)}},
71  {amrex::Real(40.0), amrex::Real(100.0), amrex::Real(36.25), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.14), // 14 cropland/natural vegetation mosaic
72  {amrex::Real(0.2), amrex::Real(0.3), amrex::Real(0.3), amrex::Real(0.4), amrex::Real(1.1), amrex::Real(2.5), amrex::Real(3.2), amrex::Real(2.2), amrex::Real(1.1), amrex::Real(0.3), amrex::Real(0.3), amrex::Real(0.2)}},
73  {amrex::Real(999.0), amrex::Real(999.0), amrex::Real(999.0), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.00), // 15 Snow and Ice
74  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0)}},
75  {amrex::Real(999.0), amrex::Real(999.0), amrex::Real(999.0), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.00), // 16 Barren or Sparsely Vegetated
76  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0)}},
77  {amrex::Real(100.0), amrex::Real(30.0), amrex::Real(51.75), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.00), // 17 Water
78  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0)}},
79  {amrex::Real(150.0), amrex::Real(100.0), amrex::Real(42.00), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.30), // 18 Wooded Tundra
80  {amrex::Real(1.0), amrex::Real(1.0), amrex::Real(1.1), amrex::Real(1.3), amrex::Real(1.7), amrex::Real(2.1), amrex::Real(2.1), amrex::Real(1.8), amrex::Real(1.3), amrex::Real(1.1), amrex::Real(1.0), amrex::Real(1.0)}},
81  {amrex::Real(150.0), amrex::Real(100.0), amrex::Real(42.00), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.20), // 19 Mixed Tundra
82  {amrex::Real(0.6), amrex::Real(0.6), amrex::Real(0.7), amrex::Real(0.8), amrex::Real(1.2), amrex::Real(1.8), amrex::Real(1.8), amrex::Real(1.3), amrex::Real(0.8), amrex::Real(0.7), amrex::Real(0.6), amrex::Real(0.6)}},
83  {amrex::Real(200.0), amrex::Real(100.0), amrex::Real(42.00), amrex::Real(298.0), amrex::Real(5000.0), amrex::Real(0.03), // 20 Barren Tundra
84  {amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0), amrex::Real(0.0)}},
85  };
86  if (category < 1 || category > noahmp_num_vegetation_categories) { return nullptr; }
87  return &table[category - 1];
88 }
89 
90 /**
91  * @brief Leaf area index of @p params on day of year @p day, interpolated between the
92  * monthly values as Noah-MP does for its table-LAI options (PhenologyMainMod): month
93  * index 12 * day / days_in_year, linear between the two nearest months, wrapping December
94  * to January. In the southern hemisphere Noah-MP shifts the day by half a year first;
95  * pass @p southern = true for that.
96  */
97 [[nodiscard]] inline amrex::Real
99  amrex::Real days_in_year, bool southern) noexcept
100 {
101  amrex::Real d = day;
102  if (southern) {
103  d = d + amrex::Real(0.5) * days_in_year;
104  while (d >= days_in_year) { d -= days_in_year; }
105  }
106  const amrex::Real month = amrex::Real(12.0) * d / days_in_year;
107  // Noah-MP: IntpMonth1 = MonthCurrent + 0.5 (an integer, so truncated),
108  // IntpMonth2 = IntpMonth1 + 1, weight of month 1 = (IntpMonth1 + 0.5) - MonthCurrent.
109  int m1 = static_cast<int>(month + amrex::Real(0.5));
110  int m2 = m1 + 1;
111  const amrex::Real w1 = (static_cast<amrex::Real>(m1) + amrex::Real(0.5)) - month;
112  const amrex::Real w2 = amrex::Real(1.0) - w1;
113  if (m1 < 1) { m1 = 12; }
114  if (m2 > 12) { m2 = 1; }
115  return w1 * params.lai[m1 - 1] + w2 * params.lai[m2 - 1];
116 }
117 
118 /**
119  * @brief The day of the year, as Noah-MP counts it, of a date "YYYY-MM-DD[ HH:MM[:SS]]"
120  * (ERF's start_datetime).
121  *
122  * The day is Noah-MP's DayJulianInYear, which PhenologyMainMod takes as
123  * 0 <= day < days_in_year. It is 0 at 00:00 on 1 January, and the time of day is added
124  * as a fraction. @p days_in_year is 365 or 366.
125  *
126  * @return false when the date cannot be read, the month is outside 1-12, the day is
127  * outside the month, or the time is outside 00:00:00-23:59:60.
128  */
129 [[nodiscard]] inline bool
130 noahmp_day_of_year (const std::string& datetime, amrex::Real& day,
131  amrex::Real& days_in_year)
132 {
133  int year = 0, month = 0, mday = 0, hour = 0, minute = 0;
134  double second = 0.0;
135  const int n = std::sscanf(datetime.c_str(), "%d-%d-%d %d:%d:%lf",
136  &year, &month, &mday, &hour, &minute, &second);
137  if (n < 3 || month < 1 || month > 12) { return false; }
138  if (n < 4) { hour = 0; }
139  if (n < 5) { minute = 0; }
140  if (n < 6) { second = 0.0; }
141  const bool leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
142  const int month_days[12] = {31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
143  if (mday < 1 || mday > month_days[month - 1] || hour < 0 || hour > 23 ||
144  minute < 0 || minute > 59 || second < 0.0 || second > 60.0) {
145  return false;
146  }
147  int whole = mday - 1;
148  for (int m = 0; m < month - 1; ++m) { whole += month_days[m]; }
149  day = static_cast<amrex::Real>(whole) +
150  static_cast<amrex::Real>((hour * 3600.0 + minute * 60.0 + second) / 86400.0);
151  days_in_year = leap ? amrex::Real(366) : amrex::Real(365);
152  return true;
153 }
154 
155 #endif
amrex::Real noahmp_leaf_area_index(const NoahMPVegetationParams &params, amrex::Real day, amrex::Real days_in_year, bool southern) noexcept
Leaf area index of params on day of year day, interpolated between the monthly values as Noah-MP does...
Definition: ERF_NoahMPVegetationTable.H:98
constexpr int noahmp_num_vegetation_categories
Definition: ERF_NoahMPVegetationTable.H:34
const NoahMPVegetationParams * noahmp_vegetation_params(int category) noexcept
The parameters of MODIS land-use category category (1-20), as Noah-MP reads them.
Definition: ERF_NoahMPVegetationTable.H:41
bool noahmp_day_of_year(const std::string &datetime, amrex::Real &day, amrex::Real &days_in_year)
The day of the year, as Noah-MP counts it, of a date "YYYY-MM-DD[ HH:MM[:SS]]" (ERF's start_datetime)...
Definition: ERF_NoahMPVegetationTable.H:130
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_NoahMPVegetationTable.H:23
amrex::Real lai[12]
LAI_JAN ... LAI_DEC monthly leaf area index [m^2/m^2].
Definition: ERF_NoahMPVegetationTable.H:30
amrex::Real t_opt
TOPT optimum transpiration air temperature [K].
Definition: ERF_NoahMPVegetationTable.H:27
amrex::Real rs_min
RS minimum stomatal resistance [s/m].
Definition: ERF_NoahMPVegetationTable.H:24
amrex::Real z0
Z0MVT momentum roughness length of the vegetation [m].
Definition: ERF_NoahMPVegetationTable.H:29
amrex::Real hs
HS vapour pressure deficit parameter [1/(kg/kg)].
Definition: ERF_NoahMPVegetationTable.H:26
amrex::Real rgl
RGL radiation stress parameter [W/m^2].
Definition: ERF_NoahMPVegetationTable.H:25
amrex::Real rs_max
RSMAX maximum stomatal resistance [s/m].
Definition: ERF_NoahMPVegetationTable.H:28