Skip to main content Link Search Menu Expand Document (external link)

Implicit Flux–Profile Solution

Table of contents
  1. Code
    1. Main program
    2. Aux. programs
  2. Output
    1. Figures
    2. Text

Code

Main program

sp_16_01.m | View on GitHub
% Supplemental program 16.1

% This code replicates the multilayer canopy model of Bonan et al. (2018)
% at US-UMB.2006 for the first time step

% --- Physical constants

physcon.vkc = 0.4;                  % von Karman constant
physcon.grav = 9.80665;             % Gravitational acceleration (m/s2)
physcon.tfrz = 273.15;              % Freezing point of water (K)
physcon.hvap = 2.501e6;             % Latent heat of evaporation (J/kg)
physcon.mmh2o = 18.02 / 1000;       % Molecular mass of water (kg/mol)
physcon.mmdry = 28.97 / 1000;       % Molecular mass of dry air (kg/mol)
physcon.cpd = 1005;                 % Specific heat of dry air at constant pressure (J/kg/K)
physcon.cpw = 1846;                 % Specific heat of water vapor at constant pressure (J/kg/K)
physcon.rgas = 8.31446;             % Universal gas constant (J/K/mol)

% --- Array indices for leaves

leafvar.nleaf = 2;                  % Two leaf types (sunlit and shaded)
leafvar.isun = 1;                   % Sunlit leaf
leafvar.isha = 2;                   % Shaded leaf

% --- Define canopy structure

npts = 1;                           % Number of grid points

for p = 1:npts

   % Plant area index of canopy (m2/m2)

   surfvar.pai(p) = 5.051612734794617;

   % Atmospheric forcing reference height (m)

   forcvar.zref(p) = 46;

   % Canopy height (m)

   surfvar.hc(p) = 21;

   % Determine number of within-canopy layers by specifying height increment (m)

   dht = 0.5;

   % Define the number of within-canopy layers and adjust dht if needed

   nveg = round(surfvar.hc(p) / dht);
   dht = surfvar.hc(p) / nveg;

   % Set array indices for within canopy layers

   surfvar.nsoi(p) = 1;                                     % First layer is soil
   surfvar.nbot(p) = surfvar.nsoi(p) + 1;                   % Bottom leaf layer
   surfvar.ntop(p) = surfvar.nbot(p) + nveg - 1;            % Top leaf layer

   % Calculate heights at layer interfaces (zw). These are the heights
   % for the conductance between two scalar concentrations. They are
   % defined for ic = nsoi (ground) to ic = ntop (top of the canopy).

   ic = surfvar.ntop(p);
   surfvar.zw(p,ic) = surfvar.hc(p);
   for ic = surfvar.ntop(p)-1: -1: surfvar.nsoi(p)
      surfvar.zw(p,ic) = surfvar.zw(p,ic+1) - dht;
   end

   ic = surfvar.nsoi(p);
   if (surfvar.zw(p,ic) > 1e-10 || surfvar.zw(p,ic) < 0)
      error('zw improperly defined at ground level')
   end

   % Now calculate the above-canopy layers and their heights

   dz_to_zref = forcvar.zref(p) - surfvar.hc(p);
   n_to_zref = round(dz_to_zref / dht);
   dht = dz_to_zref / n_to_zref;
   surfvar.nlev(p) = surfvar.ntop(p) + n_to_zref;

   ic = surfvar.nlev(p);
   surfvar.zw(p,ic) = forcvar.zref(p);
   for ic = surfvar.nlev(p)-1: -1: surfvar.ntop(p)+1
      surfvar.zw(p,ic) = surfvar.zw(p,ic+1) - dht;
   end

   % Determine heights of the scalar concentration and scalar source
   % (zs). These are physically centered between the conductance points
   % (i.e., in the middle of the layer).

   ic = surfvar.nsoi(p);
   surfvar.zs(p,ic) = 0;
   for ic = surfvar.nbot(p):surfvar.nlev(p)
      surfvar.zs(p,ic) = 0.5 * (surfvar.zw(p,ic) + surfvar.zw(p,ic-1));
   end

   % Determine plant area index increment for each layer by numerically
   % integrating the plant area density (beta distribution) between
   % the bottom and top heights for that layer

   pbeta = 3.5;     % Parameter for beta distribution
   qbeta = 2.0;     % Parameter for beta distribution

   for ic = surfvar.nbot(p):surfvar.ntop(p)
      zl = surfvar.zw(p,ic-1);
      zu = surfvar.zw(p,ic);

      surfvar.dpai(p,ic) = 0;

      % Numerical integration between zl and zu using 100 sublayers

      num_int = 100;
      dz_int = (zu - zl) / num_int;
      for ic_int = 1:num_int

         if (ic_int == 1)
            z_int = zl + 0.5 * dz_int;
         else
            z_int = z_int + dz_int;
         end

         % beta distribution probability density function

         zrel = min(z_int/surfvar.hc(p), 1);
         beta_pdf = (zrel^(pbeta-1) * (1 - zrel)^(qbeta-1)) / beta(pbeta,qbeta);

         % Plant area density (m2/m3)

         pad = (surfvar.pai(p) / surfvar.hc(p)) * beta_pdf;

         % Plant area index (m2/m2)

         surfvar.dpai(p,ic) = surfvar.dpai(p,ic) + pad * dz_int;

      end
   end

   % Check to make sure sum of numerical integration matches canopy plant area index

   pai_sum = 0;
   for ic = surfvar.nbot(p):surfvar.ntop(p)
      pai_sum = pai_sum + surfvar.dpai(p,ic);
   end

   if (abs(pai_sum - surfvar.pai(p)) > 1e-06)
      error('plant area index error')
   end

   % Set layers with small plant area index to zero

   pai_miss = 0;
   for ic = surfvar.nbot(p):surfvar.ntop(p)
      if (surfvar.dpai(p,ic) < 0.01)
         pai_miss = pai_miss + surfvar.dpai(p,ic);
         surfvar.dpai(p,ic) = 0;
      end
   end

   % Distribute the missing plant area across vegetation layers
   % in proportion to the plant area profile

   if (pai_miss > 0)
      pai_old = pai_sum;
      pai_new = pai_old - pai_miss;
      for ic = surfvar.nbot(p):surfvar.ntop(p)
         surfvar.dpai(p,ic) = surfvar.dpai(p,ic) + pai_miss * (surfvar.dpai(p,ic) / pai_new);
      end
   end

   % Find the lowest vegetation layer

   for ic = surfvar.ntop(p): -1: surfvar.nbot(p)
      if (surfvar.dpai(p,ic) > 0)
         ic_bot = ic;
      end
   end
   surfvar.nbot(p) = ic_bot;

   % Zero out non-vegetation layers

   ic = surfvar.nsoi(p);
   surfvar.dpai(p,ic) = 0;

   for ic = surfvar.ntop(p)+1:surfvar.nlev(p)
      surfvar.dpai(p,ic) = 0;
   end

end

% --- Canopy layer variables

for p = 1:npts

   % Wet and dry fraction of each layer

   for ic = surfvar.nbot(p):surfvar.ntop(p)
      surfvar.fwet(p,ic) = 0;
      surfvar.fdry(p,ic) = 0.8218390792391702;
   end

   % Sunlit and shaded fraction of each layer

   Kb = 1.762817445019839;
   for ic = surfvar.ntop(p): -1: surfvar.nbot(p)
      if (ic == surfvar.ntop(p))
         sumpai = 0.5 * surfvar.dpai(p,ic);
      else
         sumpai = sumpai + 0.5 * (surfvar.dpai(p,ic+1) + surfvar.dpai(p,ic));
      end
      surfvar.fracsun(p,ic) = exp(-Kb * sumpai);
      surfvar.fracsha(p,ic) = 1 - surfvar.fracsun(p,ic);
   end

   % Leaf conductances of each layer - specify boundary layer and stomatal conductances rather than calculate

   for ic = surfvar.nbot(p):surfvar.ntop(p)
      leafvar.gbh(p,ic,leafvar.isun) = 2.268731551029694;
      leafvar.gbh(p,ic,leafvar.isha) = 2.268731551029694;
      leafvar.gbv(p,ic,leafvar.isun) = 2.496430918408511;
      leafvar.gbv(p,ic,leafvar.isha) = 2.496430918408511;
   end

   ic =  7; leafvar.gs(p,ic,leafvar.isun) = 0.1056193510550169; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic =  8; leafvar.gs(p,ic,leafvar.isun) = 0.1058669704208841; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic =  9; leafvar.gs(p,ic,leafvar.isun) = 0.1062166035088956; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 10; leafvar.gs(p,ic,leafvar.isun) = 0.1066846074875817; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 11; leafvar.gs(p,ic,leafvar.isun) = 0.1072854387286280; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 12; leafvar.gs(p,ic,leafvar.isun) = 0.1080315168674592; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 13; leafvar.gs(p,ic,leafvar.isun) = 0.1089335362366439; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 14; leafvar.gs(p,ic,leafvar.isun) = 0.1100012607812562; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 15; leafvar.gs(p,ic,leafvar.isun) = 0.1112447128077408; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 16; leafvar.gs(p,ic,leafvar.isun) = 0.1126755044648808; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 17; leafvar.gs(p,ic,leafvar.isun) = 0.1138467165585616; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 18; leafvar.gs(p,ic,leafvar.isun) = 0.1170524695200598; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 19; leafvar.gs(p,ic,leafvar.isun) = 0.1186451281076514; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 20; leafvar.gs(p,ic,leafvar.isun) = 0.1206859738130298; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 21; leafvar.gs(p,ic,leafvar.isun) = 0.1228219389652392; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 22; leafvar.gs(p,ic,leafvar.isun) = 0.1263235652964973; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 23; leafvar.gs(p,ic,leafvar.isun) = 0.1300019677357508; leafvar.gs(p,ic,leafvar.isha) = 2.0000000000000000E-003;
   ic = 24; leafvar.gs(p,ic,leafvar.isun) = 0.1322680545506565; leafvar.gs(p,ic,leafvar.isha) = 5.2146013029975334E-003;
   ic = 25; leafvar.gs(p,ic,leafvar.isun) = 0.1367071935229807; leafvar.gs(p,ic,leafvar.isha) = 5.5227688387169205E-003;
   ic = 26; leafvar.gs(p,ic,leafvar.isun) = 0.1408216759258680; leafvar.gs(p,ic,leafvar.isha) = 9.2945439124555301E-003;
   ic = 27; leafvar.gs(p,ic,leafvar.isun) = 0.1452273039039047; leafvar.gs(p,ic,leafvar.isha) = 9.4101275089266447E-003;
   ic = 28; leafvar.gs(p,ic,leafvar.isun) = 0.1499262843535941; leafvar.gs(p,ic,leafvar.isha) = 1.2582674218550544E-002;
   ic = 29; leafvar.gs(p,ic,leafvar.isun) = 0.1549264640058029; leafvar.gs(p,ic,leafvar.isha) = 1.6999874421743270E-002;
   ic = 30; leafvar.gs(p,ic,leafvar.isun) = 0.1611234013632947; leafvar.gs(p,ic,leafvar.isha) = 2.3036435105984941E-002;
   ic = 31; leafvar.gs(p,ic,leafvar.isun) = 0.1668845999057947; leafvar.gs(p,ic,leafvar.isha) = 2.7903866816023401E-002;
   ic = 32; leafvar.gs(p,ic,leafvar.isun) = 0.1727971327085968; leafvar.gs(p,ic,leafvar.isha) = 3.7385308959493969E-002;
   ic = 33; leafvar.gs(p,ic,leafvar.isun) = 0.1788628079180081; leafvar.gs(p,ic,leafvar.isha) = 4.6808450662473224E-002;
   ic = 34; leafvar.gs(p,ic,leafvar.isun) = 0.1850771375553107; leafvar.gs(p,ic,leafvar.isha) = 5.9036977283335762E-002;
   ic = 35; leafvar.gs(p,ic,leafvar.isun) = 0.1934140277837149; leafvar.gs(p,ic,leafvar.isha) = 7.1890808689035093E-002;
   ic = 36; leafvar.gs(p,ic,leafvar.isun) = 0.1998116684650200; leafvar.gs(p,ic,leafvar.isha) = 8.7547774703355410E-002;
   ic = 37; leafvar.gs(p,ic,leafvar.isun) = 0.2061626747018590; leafvar.gs(p,ic,leafvar.isha) = 0.1059444058487105;
   ic = 38; leafvar.gs(p,ic,leafvar.isun) = 0.2124795008223110; leafvar.gs(p,ic,leafvar.isha) = 0.1228398700721039;
   ic = 39; leafvar.gs(p,ic,leafvar.isun) = 0.2173241738995193; leafvar.gs(p,ic,leafvar.isha) = 0.1416660859387607;
   ic = 40; leafvar.gs(p,ic,leafvar.isun) = 0.2228796106202699; leafvar.gs(p,ic,leafvar.isha) = 0.1584170776550386;
   ic = 41; leafvar.gs(p,ic,leafvar.isun) = 0.2272584280787935; leafvar.gs(p,ic,leafvar.isha) = 0.1712280540285039;
   ic = 42; leafvar.gs(p,ic,leafvar.isun) = 0.2303662043528620; leafvar.gs(p,ic,leafvar.isha) = 0.1801048624092090;
   ic = 43; leafvar.gs(p,ic,leafvar.isun) = 0.2315636153119537; leafvar.gs(p,ic,leafvar.isha) = 0.1844507421254655;

   % Net radiation of each leaf - specify Rn rather than calculate

%  for ic = surfvar.nbot(p):surfvar.ntop(p)
%     leafvar.rnleaf(p,ic,leafvar.isun) = ...;
%     leafvar.rnleaf(p,ic,leafvar.isha) = ...;
%  end

   ic =  7; leafvar.rnleaf(p,ic,leafvar.isun) = 139.9869857739781; leafvar.rnleaf(p,ic,leafvar.isha) =  1.411488333307743;
   ic =  8; leafvar.rnleaf(p,ic,leafvar.isun) = 139.8100113537029; leafvar.rnleaf(p,ic,leafvar.isha) =  1.234513913032590;
   ic =  9; leafvar.rnleaf(p,ic,leafvar.isun) = 139.7147998761629; leafvar.rnleaf(p,ic,leafvar.isha) =  1.139302435492522;
   ic = 10; leafvar.rnleaf(p,ic,leafvar.isun) = 139.6645467566822; leafvar.rnleaf(p,ic,leafvar.isha) =  1.089049316011852;
   ic = 11; leafvar.rnleaf(p,ic,leafvar.isun) = 139.6422035725484; leafvar.rnleaf(p,ic,leafvar.isha) =  1.066706131878055;
   ic = 12; leafvar.rnleaf(p,ic,leafvar.isun) = 139.6392966303582; leafvar.rnleaf(p,ic,leafvar.isha) =  1.063799189687813;
   ic = 13; leafvar.rnleaf(p,ic,leafvar.isun) = 139.6514847604817; leafvar.rnleaf(p,ic,leafvar.isha) =  1.075987319811279;
   ic = 14; leafvar.rnleaf(p,ic,leafvar.isun) = 139.6766021357984; leafvar.rnleaf(p,ic,leafvar.isha) =  1.101104695127997;
   ic = 15; leafvar.rnleaf(p,ic,leafvar.isun) = 139.7137254254163; leafvar.rnleaf(p,ic,leafvar.isha) =  1.138227984745972;
   ic = 16; leafvar.rnleaf(p,ic,leafvar.isun) = 139.7627019640728; leafvar.rnleaf(p,ic,leafvar.isha) =  1.187204523402388;
   ic = 17; leafvar.rnleaf(p,ic,leafvar.isun) = 139.8238999626867; leafvar.rnleaf(p,ic,leafvar.isha) =  1.248402522016342;
   ic = 18; leafvar.rnleaf(p,ic,leafvar.isun) = 139.8980702313243; leafvar.rnleaf(p,ic,leafvar.isha) =  1.322572790653995;
   ic = 19; leafvar.rnleaf(p,ic,leafvar.isun) = 139.9862631887909; leafvar.rnleaf(p,ic,leafvar.isha) =  1.410765748120540;
   ic = 20; leafvar.rnleaf(p,ic,leafvar.isun) = 140.0897684653183; leafvar.rnleaf(p,ic,leafvar.isha) =  1.514271024647946;
   ic = 21; leafvar.rnleaf(p,ic,leafvar.isun) = 140.2100538053315; leafvar.rnleaf(p,ic,leafvar.isha) =  1.634556364661143;
   ic = 22; leafvar.rnleaf(p,ic,leafvar.isun) = 140.3486818847138; leafvar.rnleaf(p,ic,leafvar.isha) =  1.773184444043382;
   ic = 23; leafvar.rnleaf(p,ic,leafvar.isun) = 140.5071806149416; leafvar.rnleaf(p,ic,leafvar.isha) =  1.931683174271291;
   ic = 24; leafvar.rnleaf(p,ic,leafvar.isun) = 140.6868352048059; leafvar.rnleaf(p,ic,leafvar.isha) =  2.111337764135555;
   ic = 25; leafvar.rnleaf(p,ic,leafvar.isun) = 140.8883584829672; leafvar.rnleaf(p,ic,leafvar.isha) =  2.312861042296822;
   ic = 26; leafvar.rnleaf(p,ic,leafvar.isun) = 141.1113792315132; leafvar.rnleaf(p,ic,leafvar.isha) =  2.535881790842842;
   ic = 27; leafvar.rnleaf(p,ic,leafvar.isun) = 141.3536664423189; leafvar.rnleaf(p,ic,leafvar.isha) =  2.778169001648555;
   ic = 28; leafvar.rnleaf(p,ic,leafvar.isun) = 141.6099822011559; leafvar.rnleaf(p,ic,leafvar.isha) =  3.034484760485563;
   ic = 29; leafvar.rnleaf(p,ic,leafvar.isun) = 141.8704336551236; leafvar.rnleaf(p,ic,leafvar.isha) =  3.294936214453243;
   ic = 30; leafvar.rnleaf(p,ic,leafvar.isun) = 142.1181913093900; leafvar.rnleaf(p,ic,leafvar.isha) =  3.542693868719610;
   ic = 31; leafvar.rnleaf(p,ic,leafvar.isun) = 142.3264909566734; leafvar.rnleaf(p,ic,leafvar.isha) =  3.750993516003037;
   ic = 32; leafvar.rnleaf(p,ic,leafvar.isun) = 142.4550034158019; leafvar.rnleaf(p,ic,leafvar.isha) =  3.879505975131512;
   ic = 33; leafvar.rnleaf(p,ic,leafvar.isun) = 142.4460421185886; leafvar.rnleaf(p,ic,leafvar.isha) =  3.870544677918208;
   ic = 34; leafvar.rnleaf(p,ic,leafvar.isun) = 142.2218178601452; leafvar.rnleaf(p,ic,leafvar.isha) =  3.646320419474797;
   ic = 35; leafvar.rnleaf(p,ic,leafvar.isun) = 141.6851596824207; leafvar.rnleaf(p,ic,leafvar.isha) =  3.109662241750371;
   ic = 36; leafvar.rnleaf(p,ic,leafvar.isun) = 140.7277716843982; leafvar.rnleaf(p,ic,leafvar.isha) =  2.152274243727867;
   ic = 37; leafvar.rnleaf(p,ic,leafvar.isun) = 139.2518034108234; leafvar.rnleaf(p,ic,leafvar.isha) =  0.676305970153017;
   ic = 38; leafvar.rnleaf(p,ic,leafvar.isun) = 137.2114197261891; leafvar.rnleaf(p,ic,leafvar.isha) = -1.364077714481233;
   ic = 39; leafvar.rnleaf(p,ic,leafvar.isun) = 134.6805463548995; leafvar.rnleaf(p,ic,leafvar.isha) = -3.894951085770870;
   ic = 40; leafvar.rnleaf(p,ic,leafvar.isun) = 131.9550915266485; leafvar.rnleaf(p,ic,leafvar.isha) = -6.620405914021802;
   ic = 41; leafvar.rnleaf(p,ic,leafvar.isun) = 129.7361873094630; leafvar.rnleaf(p,ic,leafvar.isha) = -8.839310131207370;
   ic = 42; leafvar.rnleaf(p,ic,leafvar.isun) = 129.7993862020948; leafvar.rnleaf(p,ic,leafvar.isha) = -8.776111238575538;
   ic = 43; leafvar.rnleaf(p,ic,leafvar.isun) = 143.7045065806239; leafvar.rnleaf(p,ic,leafvar.isha) =  5.129009139953610;

   % Leaf heat capacity

   for ic = surfvar.nbot(p):surfvar.ntop(p)
      leafvar.cpleaf(p,ic) = 744.5333333333334;
   end

end

% --- Soil variables

for p = 1:npts
   soilvar.tk(p) = 1.261326601469150;          % Soil thermal conductivity (W/m/K)
   soilvar.dz(p) = 7.1006354171935350E-003;    % Soil layer depth (m)
   soilvar.tsoi(p) = 294.8492736816406;        % Soil temperature (K)
   soilvar.resis(p) = 3361.509423807650;       % Soil evaporative resistance (s/m)
   soilvar.rhg(p) = 0.9984057411945876;        % Relative humidity of airspace at soil surface (fraction)
   fluxvar.rnsoi(p) = 1.896127799819662;       % Net radiation at ground (W/m2)
end

% --- Atmospheric forcing at a reference height

for p = 1:npts

   forcvar.pref(p) = 98620;               % Atmospheric pressure (Pa)
   forcvar.uref(p) = 5.169;               % Wind speed at reference height (m/s)
   forcvar.tref(p) = 295.9349938964844;   % Air temperature at reference height (K)

   % Water vapor (mol/mol) using constant relative humidity

   relhum = 53.871;
   [esat, desat] = satvap (forcvar.tref(p)-physcon.tfrz);
   eref = (relhum / 100) * esat;
   forcvar.qref(p) = eref / forcvar.pref(p);

   % Specific humidity (kg/kg)

   qref_kg_kg = physcon.mmh2o / physcon.mmdry * eref / (forcvar.pref(p) - (1 - physcon.mmh2o/physcon.mmdry) * eref);

   % Potential temperature and virtual potential temperature (K)

   forcvar.thref(p) = forcvar.tref(p) + 0.0098 * forcvar.zref(p);
   forcvar.thvref(p) = forcvar.thref(p) * (1 + 0.61 * qref_kg_kg);

   % Molar density (mol/m3)

   forcvar.rhomol(p) = forcvar.pref(p) / (physcon.rgas * forcvar.tref(p));

   % Air density (kg/m3)

   rhoair = forcvar.rhomol(p) * physcon.mmdry * (1 - (1 - physcon.mmh2o/physcon.mmdry) * eref / forcvar.pref(p));

   % Molecular mass of air (kg/mol)

   forcvar.mmair(p) = rhoair / forcvar.rhomol(p);

   % Specific heat of air at constant pressure (J/mol/K)

   forcvar.cpair(p) = physcon.cpd * (1 + (physcon.cpw/physcon.cpd - 1) * qref_kg_kg) * forcvar.mmair(p);

end

% --- Time step (s)

dt = 5 * 60;

% --- Initialize profiles

for p = 1:npts
   for ic = surfvar.nsoi(p):surfvar.nlev(p)
      fluxvar.tair_old(p,ic) = forcvar.tref(p);
      fluxvar.qair_old(p,ic) = forcvar.qref(p);
      fluxvar.tveg_old(p,ic,leafvar.isun) = forcvar.tref(p);
      fluxvar.tveg_old(p,ic,leafvar.isha) = forcvar.tref(p);
   end
  fluxvar.taf(p) = fluxvar.tair_old(p,surfvar.ntop(p));
  fluxvar.qaf(p) = fluxvar.qair_old(p,surfvar.ntop(p));
end

% Initialize profile output variables

for p = 1:npts

   for ic = surfvar.nsoi(p):surfvar.nlev(p)
      fluxvar.wind(p,ic) = 0;
      fluxvar.tair(p,ic) = 0;
      fluxvar.qair(p,ic) = 0;
      fluxvar.tveg(p,ic,leafvar.isun) = 0;
      fluxvar.tveg(p,ic,leafvar.isha) = 0;
      fluxvar.ga_prof(p,ic) = 0;
   end

   for ic = surfvar.nbot(p):surfvar.ntop(p)
      fluxvar.shveg(p,ic) = 0;
      fluxvar.etveg(p,ic) = 0;
      fluxvar.stveg(p,ic) = 0;
   end

   for ic = surfvar.nbot(p):surfvar.nlev(p)
      fluxvar.shair(p,ic) = 0;
      fluxvar.etair(p,ic) = 0;
      fluxvar.stair(p,ic) = 0;
   end

end

% --- Calculate fluxes and scalar profiles

for p = 1:npts
   surfvar.p = p;
   [fluxvar] = canopy_turbulence (dt, physcon, forcvar, surfvar, leafvar, soilvar, fluxvar);
end

% --- Write profile output data

p = 1;
for ic = surfvar.nsoi(p):surfvar.nlev(p)
   fprintf('%6.0f %10.3f %10.3f %10.3f %10.3f %10.3f %10.3f %10.3f\n',ic-1,surfvar.zs(p,ic),surfvar.dpai(p,ic), ...
   fluxvar.wind(p,ic),fluxvar.tair(p,ic),fluxvar.qair(p,ic)*forcvar.pref(p), ...
   fluxvar.tveg(p,ic,leafvar.isun),fluxvar.tveg(p,ic,leafvar.isha))
end

% --- Make graph profile output data

p = 1;
z = surfvar.zs(p,:) / surfvar.hc(p);
x = fluxvar.tair(p,:) - 273.15;

plot(x,z)
title('Profiles')
xlabel('Air temperature (^oC)')
ylabel('Relative height')

Aux. programs

brent_root.m | View on GitHub
function [fluxvar, root] = brent_root (func, physcon, forcvar, surfvar, fluxvar, xa, xb, tol)

% Use Brent's method to find the root of a function, which is known to exist between
% xa and xb. The root is updated until its accuracy is tol. func is the name of the
% function to solve. The variable root is returned as the root of the function. The
% function being evaluated has the definition statement: 
%
% function [fluxvar, fx] = func (physcon, forcvar, surfvar, fluxvar, x)
%
% The function func is exaluated at x and the returned value is fx. It uses variables
% in the physcon, forcvar, surfvar, and fluxvar structures. These are passed in as
% input arguments. It also calculates values for variables in the fluxvar structure
% so this must be returned in the function call as an output argument. The matlab
% function feval evaluates func.

% --- Evaluate func at xa and xb and make sure the root is bracketed

a = xa;
b = xb;
[fluxvar, fa] = feval(func, physcon, forcvar, surfvar, fluxvar, a);
[fluxvar, fb] = feval(func, physcon, forcvar, surfvar, fluxvar, b);

if ((fa > 0 & fb > 0) | (fa < 0 & fb < 0))
   error('brent_root error: root must be bracketed')
end

% --- Initialize iteration

itmax = 50;      % Maximum number of iterations
eps1 = 1e-08;    % Relative error tolerance

c = b;
fc = fb;

% --- Iterative root calculation

for iter = 1:itmax
   if ((fb > 0 & fc > 0) | (fb < 0 & fc < 0))
      c = a;
      fc = fa;
      d = b - a;
      e = d;
   end
   if (abs(fc) < abs(fb))
      a = b;
      b = c;
      c = a;
      fa = fb;
      fb = fc;
      fc = fa;
   end
   tol1 = 2 * eps1 * abs(b) + 0.5 * tol;
   xm = 0.5 * (c - b);

   % Check to end iteration

   if (abs(xm) <= tol1 | fb == 0)
      break
   end

   if (abs(e) >= tol1 & abs(fa) > abs(fb))
      s = fb / fa;
      if (a == c)
         p = 2 * xm * s;
         q = 1 - s;
      else
         q = fa / fc;
         r = fb / fc;
         p = s * (2 * xm * q * (q - r) - (b - a) * (r - 1));
         q = (q - 1) * (r - 1) * (s - 1);
      end
      if (p > 0)
         q = -q;
      end
      p = abs(p);
      if (2*p < min(3*xm*q-abs(tol1*q), abs(e*q)))
         e = d;
         d = p / q;
      else
         d = xm;
         e = d;
      end
   else
      d = xm;
      e = d;
   end
   a = b;
   fa = fb;
   if (abs(d) > tol1)
      b = b + d;
   else
      if (xm >= 0)
         b = b + abs(tol1);
      else
         b = b - abs(tol1);
      end
   end
   [fluxvar, fb] = feval(func, physcon, forcvar, surfvar, fluxvar, b);

   % Check to end iteration

   if (fb == 0)
      break
   end

   % Check to see if failed to converge

   if (iter == itmax)
      error('brent_root error: Maximum number of interations exceeded')
   end

end
root = b;
canopy_turbulence.m | View on GitHub
function [fluxvar] = canopy_turbulence (dt, physcon, forcvar, surfvar, leafvar, soilvar, fluxvar)

% Canopy turbulence, aeorodynamic conductances, and wind/temperature/water vapor
% profiles using above- and within-canopy coupling with a roughness sublayer
% (RSL) parameterization 

% ------------------------------------------------------------------------------------
% Input
%   dt                  ! Model time step (s)
%   physcon.vkc         ! von Karman constant
%   physcon.grav        ! Gravitational acceleration (m/s2)
%   physcon.mmh2o       ! Molecular mass of water (kg/mol)
%   physcon.tfrz        ! Freezing point of water (K)
%   physcon.hvap        ! Latent heat of evaporation (J/kg)
%
%   forcvar.zref        ! Reference height (m)
%   forcvar.uref        ! Wind speed at reference height (m/s)
%   forcvar.thref       ! Potential temperature at reference height (K)
%   forcvar.thvref      ! Virtual potential temperature at reference height (K)
%   forcvar.qref        ! Water vapor at reference height (mol/mol)
%   forcvar.pref        ! Atmospheric pressure (Pa)
%   forcvar.mmair       ! Molecular mass of air at reference height (kg/mol)
%   forcvar.cpair       ! Specific heat of air at constant pressure (J/mol/K)
%   forcvar.rhomol      ! Molar density (mol/m3)
%
%   surfvar.p           ! Index for grid point to process
%   surfvar.hc          ! Canopy height (m)
%   surfvar.pai         ! Canopy plant area index (m2/m2)
%   surfvar.nlev        ! Index for top level
%   surfvar.ntop        ! Index for top leaf layer
%   surfvar.nsoi        ! First canopy layer is soil
%   surfvar.zs          ! Canopy height for scalar concentration and source (m)
%   surfvar.zw          ! Canopy height at layer interfaces (m)
%   surfvar.dpai        ! Layer plant area index (m2/m2)
%   surfvar.fwet        ! Fraction of plant area index that is wet
%   surfvar.fdry        ! Fraction of plant area index that is green and dry
%   surfvar.fracsun     ! Sunlit fraction of canopy layer
%   surfvar.fracsha     ! Shaded fraction of canopy layer
%
%   leafvar.nleaf       ! Number of leaf types (sunlit and shaded)
%   leafvar.isun        ! Sunlit leaf index
%   leafvar.isha        ! Shaded leaf index
%   leafvar.gbh         ! Leaf boundary layer conductance, heat (mol/m2 leaf/s)
%   leafvar.gbv         ! Leaf boundary layer conductance, H2O (mol H2O/m2 leaf/s)
%   leafvar.gs          ! Leaf stomatal conductance (mol H2O/m2 leaf/s)
%   leafvar.cpleaf      ! Leaf heat capacity (J/m2 leaf/K)
%   leafvar.rnleaf      ! Leaf net radiation (W/m2 leaf)
%
%   soilvar.tk          ! Soil thermal conductivity (W/m/K)
%   soilvar.dz          ! Soil layer depth (m)
%   soilvar.tsoi        ! Soil temperature (K)
%   soilvar.resis       ! Soil evaporative resistance (s/m)
%   soilvar.rhg         ! Relative humidity of airspace at soil surface (fraction)
%
%   fluxvar.rnsoi       ! Net radiation at ground (W/m2)
%   fluxvar.tair_old    ! Air temperature profile for previous timestep (K)
%   fluxvar.qair_old    ! Water vapor profile for previous timestep (mol/mol)
%   fluxvar.tveg_old    ! Vegetation temperature profile for previous timestep (K)
%
% Input/output
%   fluxvar.taf         ! Air temperature at canopy top (K)
%   fluxvar.qaf         ! Water vapor at canopy top (mol/mol)
%
% Output (specific to canopy_turbulence)
%   fluxvar.Lc          ! Canopy density length scale (m)
%   fluxvar.wind        ! Wind speed profile (m/s)
%   fluxvar.uaf         ! Wind speed at canopy top (m/s)
%   fluxvar.ga_prof     ! Canopy layer aerodynamic conductance for scalars (mol/m2/s)
%
% Output (from obukhov_function)
%   fluxvar.c1m         ! Roughness sublayer c1 parameter for momentum (dimensionless)
%   fluxvar.c1c         ! Roughness sublayer c1 parameter for scalars (dimensionless)
%   fluxvar.c2          ! Roughness sublayer depth scale multiplier (dimensionless)
%   fluxvar.disp        ! Displacement height (m)
%   fluxvar.beta        ! u* / u(hc)
%   fluxvar.PrSc        ! Prandtl (Schmidt) number at canopy top
%   fluxvar.ustar       ! Friction velocity (m/s)
%   fluxvar.tstar       ! Temperature scale (K)
%   fluxvar.qstar       ! Water vapor scale (mol/mol)
%   fluxvar.gac         ! Aerodynamic conductance for a scalar above canopy (mol/m2/s)
%   fluxvar.obu_ustar   ! Obukhov length used for u* (m)
%   fluxvar.obu         ! Value for Obukhov length (m)
%
% Output (from scalar_profile)
%   fluxvar.tair        ! Air temperature profile (K)
%   fluxvar.qair        ! Water vapor profile (mol/mol)
%   fluxvar.tveg        ! Vegetation temperature profile (K)
%   fluxvar.shsoi       ! Ground sensible heat flux, ground (W/m2)
%   fluxvar.etsoi       ! Ground evaporation flux (mol H2O/m2/s)
%   fluxvar.gsoi        ! Soil heat flux (W/m2)
%   fluxvar.tg          ! Soil surface temperature (K)
%   fluxvar.shveg       ! Leaf sensible heat flux (W/m2 leaf)
%   fluxvar.etveg       ! Leaf evapotranspiration flux (mol H2O/m2 leaf/s)
%   fluxvar.stveg       ! Leaf storage heat flux (W/m2 leaf)
%   fluxvar.shair       ! Canopy air sensible heat flux (W/m2)
%   fluxvar.etair       ! Canopy air water vapor flux (mol H2O/m2/s)
%   fluxvar.stair       ! Canopy air storage heat flux (W/m2)
% ------------------------------------------------------------------------------------

% --- Index for grid point to process

p = surfvar.p;

% --- Leaf drag coefficient (dimensionless)

cd = 0.25;

% --- Canopy density length scale (m)

fluxvar.Lc(p) = surfvar.hc(p) / (cd * surfvar.pai(p));

% --- Calculate the Obukhov length

% Calculate the Obukhov length (obu) for the current surface temperature
% and surface vapor pressure using Harman & Finnigan (2007, 2008) roughness 
% sublayer (RSL) theory. Use the function "obukhov_function" to iterate obu
% until the change in obu is less than tol.

obu0 = 100;                     % Initial estimate for Obukhov length (m)
obu1 = -100;                    % Initial estimate for Obukhov length (m)
tol = 0.01;                     % Accuracy tolerance for Obukhov length (m)
func_name = 'obukhov_function'; % The function is the file obukhov_function.m

% Solve for the Obukhov length. Do not use final returned value for obu.
% Instead, use the value used to calculate u*

[fluxvar, oburoot] = hybrid_root (func_name, physcon, forcvar, surfvar, fluxvar, obu0, obu1, tol);
fluxvar.obu(p) = fluxvar.obu_ustar(p);

% --- Wind profile (m/s)

% Above-canopy wind speed: defined at zs

h_minus_d = surfvar.hc(p) - fluxvar.disp(p);
[psi_m_hc] = psi_m_monin_obukhov (h_minus_d / fluxvar.obu(p));
[psi_m_rsl_hc] = psi_m_rsl (h_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1m(p), fluxvar.c2);

for ic = surfvar.ntop(p)+1:surfvar.nlev(p)
   z_minus_d = surfvar.zs(p,ic) - fluxvar.disp(p);
   [psi_m_zs] = psi_m_monin_obukhov (z_minus_d / fluxvar.obu(p));
   [psi_m_rsl_zs] = psi_m_rsl (z_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1m(p), fluxvar.c2);
   psim = -psi_m_zs + psi_m_hc + psi_m_rsl_zs - psi_m_rsl_hc + physcon.vkc / fluxvar.beta(p);
   fluxvar.wind(p,ic) = fluxvar.ustar(p) / physcon.vkc * (log(z_minus_d/h_minus_d) + psim);
end

% Wind speed at top of canopy

fluxvar.uaf(p) = fluxvar.ustar(p) / fluxvar.beta(p);

% Within-canopy wind speed: defined at zs. Limit to > 0.1 m/s

lm = 2 * fluxvar.beta(p)^3 * fluxvar.Lc(p);
lm_over_beta = lm / fluxvar.beta(p);
for ic = surfvar.nsoi(p)+1:surfvar.ntop(p)
   fluxvar.wind(p,ic) = fluxvar.uaf(p) * exp((surfvar.zs(p,ic) - surfvar.hc(p)) / lm_over_beta);
   fluxvar.wind(p,ic) = max(fluxvar.wind(p,ic), 0.1);
end

% Wind speed at ground

fluxvar.wind(p,surfvar.nsoi(p)) = 0;

% --- Aerodynamic conductances (mol/m2/s) between zs(i) and zs(i+1)

% Above-canopy conductances

h_minus_d = surfvar.hc(p) - fluxvar.disp(p);
[psi_c_hc] = psi_c_monin_obukhov (h_minus_d / fluxvar.obu(p));
[psi_c_rsl_hc] = psi_c_rsl (h_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1c(p), fluxvar.c2);

for ic = surfvar.ntop(p)+1:surfvar.nlev(p)-1

   % Lower height zs(i)

   z_minus_d = surfvar.zs(p,ic) - fluxvar.disp(p);
   [psi_c_zs] = psi_c_monin_obukhov (z_minus_d / fluxvar.obu(p));
   [psi_c_rsl_zs] = psi_c_rsl (z_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1c(p), fluxvar.c2);
   psic1 = -psi_c_zs + psi_c_hc + psi_c_rsl_zs - psi_c_rsl_hc;

   % Upper height zs(i+1)

   z_minus_d = surfvar.zs(p,ic+1) - fluxvar.disp(p);
   [psi_c_zs] = psi_c_monin_obukhov (z_minus_d / fluxvar.obu(p));
   [psi_c_rsl_zs] = psi_c_rsl (z_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1c(p), fluxvar.c2);
   psic2 = -psi_c_zs + psi_c_hc + psi_c_rsl_zs - psi_c_rsl_hc;

   % Conductance. Note that psic = psic2 - psic1 is equivalent to:
   % -psi_c_z2 + psi_c_z1 + psi_c_rsl_z2 - psi_c_rsl_z1

   psic = psic2 - psic1;
   zlog = log((surfvar.zs(p,ic+1)-fluxvar.disp(p)) / (surfvar.zs(p,ic)-fluxvar.disp(p)));
   fluxvar.ga_prof(p,ic) = forcvar.rhomol(p) * physcon.vkc * fluxvar.ustar(p) / (zlog + psic);
end

% Special case for the top layer to the reference height

ic = surfvar.nlev(p);
z_minus_d = surfvar.zs(p,ic) - fluxvar.disp(p);
[psi_c_zs] = psi_c_monin_obukhov (z_minus_d / fluxvar.obu(p));
[psi_c_rsl_zs] = psi_c_rsl (z_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1c(p), fluxvar.c2);
psic1 = -psi_c_zs + psi_c_hc + psi_c_rsl_zs - psi_c_rsl_hc;

z_minus_d = forcvar.zref(p) - fluxvar.disp(p);
[psi_c_zs] = psi_c_monin_obukhov (z_minus_d / fluxvar.obu(p));
[psi_c_rsl_zs] = psi_c_rsl (z_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1c(p), fluxvar.c2);
psic2 = -psi_c_zs + psi_c_hc + psi_c_rsl_zs - psi_c_rsl_hc;

psic = psic2 - psic1;
zlog = log((forcvar.zref(p)-fluxvar.disp(p)) / (surfvar.zs(p,ic)-fluxvar.disp(p)));
fluxvar.ga_prof(p,ic) = forcvar.rhomol(p) * physcon.vkc * fluxvar.ustar(p) / (zlog + psic);

% Within-canopy aerodynamic conductances

for ic = surfvar.nsoi(p)+1:surfvar.ntop(p)-1
   zl = surfvar.zs(p,ic) - surfvar.hc(p);
   zu = surfvar.zs(p,ic+1) - surfvar.hc(p);
   res = fluxvar.PrSc(p) / (fluxvar.beta(p) * fluxvar.ustar(p)) * (exp(-zl/lm_over_beta) - exp(-zu/lm_over_beta));
   fluxvar.ga_prof(p,ic) = forcvar.rhomol(p) / res;
end

% Special case for top canopy layer: conductance from zs to hc

ic = surfvar.ntop(p);
zl = surfvar.zs(p,ic) - surfvar.hc(p);
zu = surfvar.hc(p) - surfvar.hc(p);
res = fluxvar.PrSc(p) / (fluxvar.beta(p) * fluxvar.ustar(p)) * (exp(-zl/lm_over_beta) - exp(-zu/lm_over_beta));
ga_below_hc = forcvar.rhomol(p) / res;

% Now include additional conductance from hc to first atmospheric layer

z_minus_d = surfvar.hc(p) - fluxvar.disp(p);
[psi_c_zs] = psi_c_monin_obukhov (z_minus_d / fluxvar.obu(p));
[psi_c_rsl_zs] = psi_c_rsl (z_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1c(p), fluxvar.c2);
psic1 = -psi_c_zs + psi_c_hc + psi_c_rsl_zs - psi_c_rsl_hc;

z_minus_d = surfvar.zs(p,ic+1) - fluxvar.disp(p);
[psi_c_zs] = psi_c_monin_obukhov (z_minus_d / fluxvar.obu(p));
[psi_c_rsl_zs] = psi_c_rsl (z_minus_d, h_minus_d, fluxvar.obu(p), fluxvar.c1c(p), fluxvar.c2);
psic2 = -psi_c_zs + psi_c_hc + psi_c_rsl_zs - psi_c_rsl_hc;

psic = psic2 - psic1;
zlog = log((surfvar.zs(p,ic+1)-fluxvar.disp(p)) / (surfvar.hc(p)-fluxvar.disp(p)));
ga_above_hc = forcvar.rhomol(p) * physcon.vkc * fluxvar.ustar(p) / (zlog + psic);
fluxvar.ga_prof(p,ic) = 1 / (1 / ga_below_hc + 1 / ga_above_hc);

% Make sure above-canopy aerodynamic resistances sum to 1/gac

sumres = 1 / ga_above_hc;
for ic = surfvar.ntop(p)+1:surfvar.nlev(p)
   sumres = sumres + 1 / fluxvar.ga_prof(p,ic);
end

if (abs(1/sumres - fluxvar.gac(p)) > 1e-06)
   error('canopy_turbulence: above-canopy aerodynamic conductance error')
end

% Aerodynamic conductance at ground

ic = surfvar.nsoi(p);
ic_plus_one = surfvar.nsoi(p)+1;

z0m_g = 0.01;                         % Roughness length of ground (m)
z0c_g = 0.1 * z0m_g;                  % Roughness length for scalars
zlog_m = log(surfvar.zs(p,ic_plus_one)/z0m_g);
zlog_c = log(surfvar.zs(p,ic_plus_one)/z0c_g);
ustar_g = fluxvar.wind(p,ic_plus_one) * physcon.vkc / zlog_m;
ustar_g = max(ustar_g, 0.01);
res = zlog_c / (physcon.vkc * ustar_g);
fluxvar.ga_prof(p,ic) = forcvar.rhomol(p) / res;

% --- Limit resistances to < 500 s/m

for ic = surfvar.nsoi(p):surfvar.nlev(p)
   res = min (forcvar.rhomol(p)/fluxvar.ga_prof(p,ic), 500);
   fluxvar.ga_prof(p,ic) = forcvar.rhomol(p) / res;
end

% --- Calculate within-canopy scalar profiles for temperature and vapor pressure

[fluxvar] = scalar_profile (dt, p, physcon, forcvar, surfvar, leafvar, soilvar, fluxvar);

% --- Temperature and water vapor at top of canopy

fluxvar.taf(p) = fluxvar.tair(p,surfvar.ntop(p));
fluxvar.qaf(p) = fluxvar.qair(p,surfvar.ntop(p));
hybrid_root.m | View on GitHub
function [fluxvar, root] = hybrid_root (func, physcon, forcvar, surfvar, fluxvar, xa, xb, tol)

% Solve for the root of a function using the secant and Brent's methods given
% initial estimates xa and xb. The root is updated until its accuracy is tol. 
% func is the name of the function to solve. The variable root is returned as
% the root of the function. The function being evaluated has the definition statement: 
%
% function [fluxvar, fx] = func (physcon, forcvar, surfvar, fluxvar, x)
%
% The function func is exaluated at x and the returned value is fx. It uses variables
% in the physcon, forcvar, surfvar, and fluxvar structures. These are passed in as
% input arguments. It also calculates values for variables in the fluxvar structure
% so this must be returned in the function call as an output argument. The matlab
% function feval evaluates func.

% --- Evaluate func at xa and see if this is the root

x0 = xa;
[fluxvar, f0] = feval(func, physcon, forcvar, surfvar, fluxvar, x0);
if (f0 == 0)
   root = x0;
   return
end

% --- Evaluate func at xb and see if this is the root

x1 = xb;
[fluxvar, f1] = feval(func, physcon, forcvar, surfvar, fluxvar, x1);
if (f1 == 0)
   root = x1;
   return
end

% --- Order initial root estimates correctly

if (f1 < f0)
   minx = x1;
   minf = f1;
else
   minx = x0;
   minf = f0;
end

% --- Iterative root calculation. Use the secant method, with Brent's method as a backup

itmax = 40;
for iter = 1:itmax
   dx = -f1 * (x1 - x0) / (f1 - f0);
   x = x1 + dx;

   % Check if x is the root. If so, exit the iteration

   if (abs(dx) < tol)
      x0 = x;
      break
   end

   % Evaluate the function at x

   x0 = x1;
   f0 = f1;
   x1 = x;
   [fluxvar, f1] = feval(func, physcon, forcvar, surfvar, fluxvar, x1);
   if (f1 < minf)
      minx = x1;
      minf = f1;
   end

   % If a root zone is found, use Brent's method for a robust backup strategy
   % and exit the iteration

   if (f1 * f0 < 0)
      [fluxvar, x] = brent_root (func, physcon, forcvar, surfvar, fluxvar, x0, x1, tol);
      x0 = x;
      break
   end

   % In case of failing to converge within itmax iterations stop at the minimum function

   if (iter == itmax)
      [fluxvar, f1] = feval(func, physcon, forcvar, surfvar, fluxvar, minx);
      x0 = minx;
   end

end

root = x0;
obukhov_function.m | View on GitHub
function [fluxvar, fx] = obukhov_function (physcon, forcvar, surfvar, fluxvar, x)

% Use Harman & Finnigan (2007, 2008) roughness sublayer (RSL) theory to obtain
% the Obukhov length (obu). This is the function to solve for the Obukhov
% length. For the current estimate of the Obukhov length (x), calculate
% u*, T*, and q* and then the new length (obu). The function value is the
% change in Obukhov length: fx = x - obu.

% -------------------------------------------------------------------------
% Input
%   x                   ! Current estimate for Obukhov length (m)
%   physcon.vkc         ! von Karman constant
%   physcon.grav        ! Gravitational acceleration (m/s2)
%   physcon.mmh2o       ! Molecular mass of water (kg/mol)
%   forcvar.zref        ! Reference height (m)
%   forcvar.uref        ! Wind speed at reference height (m/s)
%   forcvar.thref       ! Potential temperature at reference height (K)
%   forcvar.thvref      ! Virtual potential temperature at reference height (K)
%   forcvar.qref        ! Water vapor at reference height (mol/mol)
%   forcvar.rhomol      ! Molar density (mol/m3)
%   forcvar.mmair       ! Molecular mass of air at reference height (kg/mol)
%   fluxvar.taf         ! Air temperature at canopy top (K)
%   fluxvar.qaf         ! Water vapor at canopy top (mol/mol)
%   fluxvar.Lc          ! Canopy density length scale (m)
%   surfvar.hc          ! Canopy height (m)
%   surfvar.p           ! Index for grid point to process
%
% Output
%   fluxvar.c1m         ! Roughness sublayer c1 parameter for momentum (dimensionless)
%   fluxvar.c1c         ! Roughness sublayer c1 parameter for scalars (dimensionless)
%   fluxvar.c2          ! Roughness sublayer depth scale multiplier (dimensionless)
%   fluxvar.disp        ! Displacement height (m)
%   fluxvar.beta        ! u* / u(hc)
%   fluxvar.PrSc        ! Prandtl (Schmidt) number at canopy top
%   fluxvar.ustar       ! Friction velocity (m/s)
%   fluxvar.tstar       ! Temperature scale (K)
%   fluxvar.qstar       ! Water vapor scale (mol/mol)
%   fluxvar.gac         ! Aerodynamic conductance for a scalar above canopy (mol/m2/s)
%   fluxvar.obu_ustar   ! Obukhov length used for u* (m)
%   fluxvar.obu         ! Value for Obukhov length (m)
%   fx                  ! Change in Obukhov length (x - obu)
% -------------------------------------------------------------------------

% --- Index for grid point to process

p = surfvar.p;

% --- Prevent near-zero values of Obukhov length

if (abs(x) <= 0.1)
   x = 0.1;
end

% --- Determine beta_val = u* / u(hc) for the current Obukhov length

% Neutral value for beta = u* / u(hc)

beta_neutral = 0.35;

% Lc/obu

LcL = fluxvar.Lc(p)/x;

if (LcL <= 0)

   % The unstable case is a quadratic equation for beta^2 at LcL

   a = 1;
   b = 16 * LcL * beta_neutral^4;
   c = -beta_neutral^4;
   beta_val = sqrt((-b + sqrt(b^2 - 4 * a * c)) / (2 * a));

   % Error check

   y = beta_val^2 * LcL;
   fy = (1 - 16 * y)^(-0.25);
   err = beta_val * fy - beta_neutral;
   if (abs(err) > 1e-10)
      error('obukhov_function: unstable case - error in beta')
   end

else

   % The stable case is a cubic equation for beta at LcL

   a = 5 * LcL;
   b = 0;
   c = 1;
   d = -beta_neutral;
   q = (2*b^3 - 9*a*b*c + 27*(a^2)*d)^2 - 4*(b^2 - 3*a*c)^3;
   q = sqrt(q);
   r = 0.5 * (q + 2*b^3 - 9*a*b*c + 27*(a^2)*d);
   r = r^(1/3);
   beta_val = -(b+r)/(3*a) - (b^2 - 3*a*c)/(3*a*r);

   % Error check

   y = beta_val^2 * LcL;
   fy = 1 + 5 * y;
   err = beta_val * fy - beta_neutral;
   if (abs(err) > 1e-10)
      error('obukhov_function: stable case - error in beta')
   end

end

% Place limits on beta

beta_val = min(0.5, max(beta_val,0.2));
fluxvar.beta(p) = beta_val;

% --- For current beta = u*/u(hc) determine displacement height

dp = beta_val^2 * fluxvar.Lc(p);               % dp = hc - disp
fluxvar.disp(p) = max(surfvar.hc(p) - dp, 0);  % Displacement height (m)

% Save reference height and canopy height (relative to displacement height)
% because these are used many times

z_minus_d = forcvar.zref(p) - fluxvar.disp(p);
h_minus_d = surfvar.hc(p) - fluxvar.disp(p);

% --- Turbulent Prandlt (Schmidt) number (PrSc) at canopy height

PrSc0 = 0.5;        % Neutral value for Pr (Sc)
PrSc1 = 0.3;        % Magnitude of variation of Pr (Sc) with stability
PrSc2 = 2.0;        % Scale of variation of Pr (Sc) with stability

fluxvar.PrSc(p) = PrSc0 + PrSc1 * tanh(PrSc2*fluxvar.Lc(p)/x);

% --- Calculate the parameters c1 and c2 needed for the RSL function phi_hat

% Evaluate Monin-Obukhov phi functions at (hc-disp)/obu

[phi_m_hc] = phi_m_monin_obukhov (h_minus_d / x);
[phi_c_hc] = phi_c_monin_obukhov (h_minus_d / x);

% Roughness sublayer depth scale multiplier (dimensionless)

fluxvar.c2 = 0.5;

% c1 for momentum and scalars (dimensionless)

fluxvar.c1m(p) = (1 -                 physcon.vkc / (2 * beta_val * phi_m_hc)) * exp(fluxvar.c2/2);
fluxvar.c1c(p) = (1 - fluxvar.PrSc(p)*physcon.vkc / (2 * beta_val * phi_c_hc)) * exp(fluxvar.c2/2);

% --- Evaluate the roughness sublayer psi_hat functions for momentum and scalars

% These are calculated at the reference height and at the canopy height. Note that
% here the heights are adjusted for the displacement height before the integration.

[psi_m_rsl_zref] = psi_m_rsl (z_minus_d, h_minus_d, x, fluxvar.c1m(p), fluxvar.c2);  % momentum at (zref-disp)
[psi_m_rsl_hc]   = psi_m_rsl (h_minus_d, h_minus_d, x, fluxvar.c1m(p), fluxvar.c2);  % momentum at (hc-disp)

[psi_c_rsl_zref] = psi_c_rsl (z_minus_d, h_minus_d, x, fluxvar.c1c(p), fluxvar.c2);  % scalars at (zref-disp)
[psi_c_rsl_hc]   = psi_c_rsl (h_minus_d, h_minus_d, x, fluxvar.c1c(p), fluxvar.c2);  % scalars at (hc-disp)

% --- Evaluate the Monin-Obukhov psi functions for momentum and scalars

% These are calculated at the reference height and at the canopy height

[psi_m_zref] = psi_m_monin_obukhov (z_minus_d / x);    % momentum at (zref-disp)/obu
[psi_m_hc]   = psi_m_monin_obukhov (h_minus_d / x);    % momentum at (hc-disp)/obu

[psi_c_zref] = psi_c_monin_obukhov (z_minus_d / x);    % scalars at (zref-disp)/obu
[psi_c_hc]   = psi_c_monin_obukhov (h_minus_d / x);    % scalars at (hc-disp)/obu

% --- Calculate u* (m/s), T* (K), q* (mol/mol), and Tv* (K)

zlog = log(z_minus_d / h_minus_d);
psim = -psi_m_zref + psi_m_hc + psi_m_rsl_zref - psi_m_rsl_hc + physcon.vkc / beta_val;
psic = -psi_c_zref + psi_c_hc + psi_c_rsl_zref - psi_c_rsl_hc;

fluxvar.ustar(p) = forcvar.uref(p) * physcon.vkc / (zlog + psim);
fluxvar.tstar(p) = (forcvar.thref(p) - fluxvar.taf(p)) * physcon.vkc / (zlog + psic);
fluxvar.qstar(p) = (forcvar.qref(p) - fluxvar.qaf(p)) * physcon.vkc / (zlog + psic);
fluxvar.obu_ustar(p) = x;

% --- Aerodynamic conductance to canopy height

fluxvar.gac(p) = forcvar.rhomol(p) * physcon.vkc * fluxvar.ustar(p) / (zlog + psic);

% --- Calculate new Obukhov length (m)

tvstar = fluxvar.tstar(p) + 0.61 * forcvar.thref(p) * fluxvar.qstar(p) * (physcon.mmh2o / forcvar.mmair(p));
fluxvar.obu(p) = fluxvar.ustar(p)^2 * forcvar.thvref(p) / (physcon.vkc * physcon.grav * tvstar);
fx = x - fluxvar.obu(p);
phi_c_monin_obukhov.m | View on GitHub
function [phi_c] = phi_c_monin_obukhov (x)

% --- Evaluate the Monin-Obukhov phi function for scalars at x

if (x < 0)
   phi_c = (1 - 16 * x)^(-0.5);
else
   phi_c = 1 + 5 * x;
end
phi_m_monin_obukhov.m | View on GitHub
function [phi_m] = phi_m_monin_obukhov (x)

% --- Evaluate the Monin-Obukhov phi function for momentum at x

if (x < 0)
   phi_m = (1 - 16 * x)^(-0.25);
else
   phi_m = 1 + 5 * x;
end
psi_c_monin_obukhov.m | View on GitHub
function [psi_c] = psi_c_monin_obukhov (x)

% --- Evaluate the Monin-Obukhov psi function for scalars at x

if (x < 0)
   y = (1 - 16 * x)^0.25;
   psi_c = 2 * log((1 + y^2)/2);
else
   psi_c = -5 * x;
end
psi_c_rsl.m | View on GitHub
function [psi_hat_c] = psi_c_rsl (z, h, L, c1, c2)

% --- Evaluate the roughness sublayer (RSL) function psi_hat for scalars
% at z. Note that z has already been adjusted for the displacement height
% (i.e., using z - d).

% ------------------------------------------------------
% Input
%   z            ! Vertical height - displacement height (m)
%   h            ! Canopy height - displacement height (m)
%   L            ! Obukhov length (m)
%   c1           ! Parameter for RSL function phi_hat (dimensionless)
%   c2           ! Parameter for RSL function phi_hat (dimensionless)
%
% Output
%   psi_hat_c    ! RSL psi_hat function for scalars (dimensionless)
% ------------------------------------------------------

% The function to integrate depends on unstable (f1) or stable (f2)

f1 = @(x) (1-16*x/L).^(-0.5) .* (1 - (1 - c1*exp(-c2*x/(2*h)))) ./ x;
f2 = @(x) (1+5*x/L)          .* (1 - (1 - c1*exp(-c2*x/(2*h)))) ./ x;

% Numerically integrate the function from z to infinity

if (L < 0)
   psi_hat_c = integral (f1, z, inf);
else
   psi_hat_c = integral (f2, z, inf);
end
psi_m_monin_obukhov.m | View on GitHub
function [psi_m] = psi_m_monin_obukhov (x)

% --- Evaluate the Monin-Obukhov psi function for momentum at x

if (x < 0)
   y = (1 - 16 * x)^0.25;
   psi_m = 2 * log((1 + y)/2) + log((1 + y^2)/2) - 2 * atan(y) + pi / 2;
else
   psi_m = -5 * x;
end
psi_m_rsl.m | View on GitHub
function [psi_hat_m] = psi_m_rsl (z, h, L, c1, c2)

% --- Evaluate the roughness sublayer (RSL) function psi_hat for momentum
% at z. Note that z has already been adjusted for the displacement height
% (i.e., using z - d).

% ------------------------------------------------------
% Input
%   z            ! Vertical height - displacement height (m)
%   h            ! Canopy height - displacement height (m)
%   L            ! Obukhov length (m)
%   c1           ! Parameter for RSL function phi_hat (dimensionless)
%   c2           ! Parameter for RSL function phi_hat (dimensionless)
%
% Output
%   psi_hat_m    ! RSL psi_hat function for momentum (dimensionless)
% ------------------------------------------------------

% The function to integrate depends on unstable (f1) or stable (f2)

f1 = @(x) (1-16*x/L).^(-0.25) .* (1 - (1 - c1*exp(-c2*x/(2*h)))) ./ x;
f2 = @(x) (1+5*x/L)           .* (1 - (1 - c1*exp(-c2*x/(2*h)))) ./ x;

% Numerically integrate the function from z to infinity

if (L < 0)
   psi_hat_m = integral (f1, z, inf);
else
   psi_hat_m = integral (f2, z, inf);
end
satvap.m | View on GitHub
function [esat, desat] = satvap (tc)

% Compute saturation vapor pressure and change in saturation vapor pressure
% with respect to temperature. Polynomial approximations are from:
% Flatau et al. (1992) Polynomial fits to saturation vapor pressure.
% Journal of Applied Meteorology 31:1507-1513. Input temperature is Celsius.

% --- For water vapor (temperature range is 0C to 100C)
 
a0 =  6.11213476;        b0 =  0.444017302;
a1 =  0.444007856;       b1 =  0.286064092e-01;
a2 =  0.143064234e-01;   b2 =  0.794683137e-03;
a3 =  0.264461437e-03;   b3 =  0.121211669e-04;
a4 =  0.305903558e-05;   b4 =  0.103354611e-06;
a5 =  0.196237241e-07;   b5 =  0.404125005e-09;
a6 =  0.892344772e-10;   b6 = -0.788037859e-12;
a7 = -0.373208410e-12;   b7 = -0.114596802e-13;
a8 =  0.209339997e-15;   b8 =  0.381294516e-16;
 
% --- For ice (temperature range is -75C to 0C)
 
c0 =  6.11123516;        d0 =  0.503277922;
c1 =  0.503109514;       d1 =  0.377289173e-01;
c2 =  0.188369801e-01;   d2 =  0.126801703e-02;
c3 =  0.420547422e-03;   d3 =  0.249468427e-04;
c4 =  0.614396778e-05;   d4 =  0.313703411e-06;
c5 =  0.602780717e-07;   d5 =  0.257180651e-08;
c6 =  0.387940929e-09;   d6 =  0.133268878e-10;
c7 =  0.149436277e-11;   d7 =  0.394116744e-13;
c8 =  0.262655803e-14;   d8 =  0.498070196e-16;

% --- Limit temperature to -75C to 100C
 
tc = min(tc, 100);
tc = max(tc, -75);

% --- Saturation vapor pressure (esat, mb) and derivative (desat, mb)

if (tc >= 0)
   esat  = a0 + tc*(a1 + tc*(a2 + tc*(a3 + tc*(a4 ...
         + tc*(a5 + tc*(a6 + tc*(a7 + tc*a8)))))));
   desat = b0 + tc*(b1 + tc*(b2 + tc*(b3 + tc*(b4 ...
         + tc*(b5 + tc*(b6 + tc*(b7 + tc*b8)))))));
else
   esat  = c0 + tc*(c1 + tc*(c2 + tc*(c3 + tc*(c4 ...
         + tc*(c5 + tc*(c6 + tc*(c7 + tc*c8)))))));
   desat = d0 + tc*(d1 + tc*(d2 + tc*(d3 + tc*(d4 ...
         + tc*(d5 + tc*(d6 + tc*(d7 + tc*d8)))))));
end

% --- Convert from mb to Pa

esat  = esat  * 100;
desat = desat * 100;
scalar_profile.m | View on GitHub
function [fluxvar] = scalar_profile (dt, p, physcon, forcvar, surfvar, leafvar, soilvar, fluxvar)

% Compute scalar profiles for temperature and water vapor using an implicit
% solution. The boundary condition is the above-canopy scalar values at the
% reference height. The vegetation and ground temperature and fluxes are
% calculated as part of the implicit solution.

% -----------------------------------------------------------------------------------
% Input
%   dt                  ! Model time step (s)
%   p                   ! Index for grid point to process
%   physcon.tfrz        ! Freezing point of water (K)
%   physcon.hvap        ! Latent heat of evaporation (J/kg)
%   physcon.mmh2o       ! Molecular mass of water (kg/mol)
%   forcvar.rhomol      ! Molar density (mol/m3)
%   forcvar.cpair       ! Specific heat of air at constant pressure (J/mol/K)
%   forcvar.pref        ! Atmospheric pressure (Pa)
%   forcvar.thref       ! Potential temperature at reference height (K)
%   forcvar.qref        ! Water vapor at reference height (mol/mol)
%   surfvar.nlev        ! Index for top level
%   surfvar.ntop        ! Index for top leaf layer
%   surfvar.nsoi        ! First canopy layer is soil
%   surfvar.zw          ! Canopy height at layer interfaces (m)
%   surfvar.dpai        ! Layer plant area index (m2/m2)
%   surfvar.fwet        ! Fraction of plant area index that is wet
%   surfvar.fdry        ! Fraction of plant area index that is green and dry
%   surfvar.fracsun     ! Sunlit fraction of canopy layer
%   surfvar.fracsha     ! Shaded fraction of canopy layer
%   leafvar.nleaf       ! Number of leaf types (sunlit and shaded)
%   leafvar.isun        ! Sunlit leaf index
%   leafvar.isha        ! Shaded leaf index
%   leafvar.gbh         ! Leaf boundary layer conductance, heat (mol/m2 leaf/s)
%   leafvar.gbv         ! Leaf boundary layer conductance, H2O (mol H2O/m2 leaf/s)
%   leafvar.gs          ! Leaf stomatal conductance (mol H2O/m2 leaf/s)
%   leafvar.cpleaf      ! Leaf heat capacity (J/m2 leaf/K)
%   leafvar.rnleaf      ! Leaf net radiation (W/m2 leaf)
%   soilvar.tk          ! Soil thermal conductivity (W/m/K)
%   soilvar.dz          ! Soil layer depth (m)
%   soilvar.tsoi        ! Soil temperature (K)
%   soilvar.resis       ! Soil evaporative resistance (s/m)
%   soilvar.rhg         ! Relative humidity of airspace at soil surface (fraction)
%   fluxvar.rnsoi       ! Net radiation at ground (W/m2)
%   fluxvar.tair_old    ! Air temperature profile for previous timestep (K)
%   fluxvar.qair_old    ! Water vapor profile for previous timestep (mol/mol)
%   fluxvar.tveg_old    ! Vegetation temperature profile for previous timestep (K)
%   fluxvar.ga_prof     ! Canopy layer aerodynamic conductance for scalars (mol/m2/s)
% Output
%   fluxvar.tair        ! Air temperature profile (K)
%   fluxvar.qair        ! Water vapor profile (mol/mol)
%   fluxvar.tveg        ! Vegetation temperature profile (K)
%   fluxvar.shsoi       ! Ground sensible heat flux, ground (W/m2)
%   fluxvar.etsoi       ! Ground evaporation flux (mol H2O/m2/s)
%   fluxvar.gsoi        ! Soil heat flux (W/m2)
%   fluxvar.tg          ! Soil surface temperature (K)
%   fluxvar.shveg       ! Leaf sensible heat flux (W/m2 leaf)
%   fluxvar.etveg       ! Leaf evapotranspiration flux (mol H2O/m2 leaf/s)
%   fluxvar.stveg       ! Leaf storage heat flux (W/m2 leaf)
%   fluxvar.shair       ! Canopy air sensible heat flux (W/m2)
%   fluxvar.etair       ! Canopy air water vapor flux (mol H2O/m2/s)
%   fluxvar.stair       ! Canopy air storage heat flux (W/m2)
% -----------------------------------------------------------------------------------

% Latent heat of vaporization (J/mol)

lambda = physcon.hvap * physcon.mmh2o;

% --------------------------------------------------------------------------
% Terms for ground temperature, which is calculated from the energy balance:
% Rn0 - H0 - lambda*E0 - G = 0
% and is rewritten as:
% T0 = alpha0*T(1) + beta0*q(1) + delta0
% --------------------------------------------------------------------------

% array index for ground

ic = surfvar.nsoi(p);

% Saturation vapor pressure and derivative at ground temperature

[esat, desat] = satvap (fluxvar.tair_old(p,ic)-physcon.tfrz); % Pa
qsat0 = esat / forcvar.pref(p);                               % Pa -> mol/mol
dqsat0 = desat / forcvar.pref(p);                             % Pa -> mol/mol

% Total soil-to-air conductance for water vapor (mol H2O/m2/s)

gsw = (1 / soilvar.resis(p)) * forcvar.rhomol(p);             % soil: s/m -> mol H2O/m2/s
gsa = fluxvar.ga_prof(p,ic);                                  % aerodynamic
gs0 = gsa * gsw / (gsa + gsw);                                % total conductance

% Terms for soil heat flux

c02 = soilvar.tk(p) / soilvar.dz(p);
c01 = -c02 * soilvar.tsoi(p);

% Coefficients for ground temperature

den = forcvar.cpair(p) * fluxvar.ga_prof(p,ic) + lambda * soilvar.rhg(p) * gs0 * dqsat0 + c02;
alpha0 = forcvar.cpair(p) * fluxvar.ga_prof(p,ic) / den;
beta0 = lambda * gs0 / den;
delta0 = (fluxvar.rnsoi(p) - lambda * soilvar.rhg(p) * gs0 * (qsat0 - dqsat0 * fluxvar.tair_old(p,ic)) - c01) / den;

% ---------------------------------------------------------------------
% alpha, beta, delta coefficients for leaf temperature:
%
% Tlsun(i) = alpha_sun(i)*T(i) + beta_sun(i)*q(i) + delta_sun(i)
% Tlsha(i) = alpha_sha(i)*T(i) + beta_sha(i)*q(i) + delta_sha(i)
%
% and leaf terms needed for scalar conservations equations. These
% are defined for all layers but the leaf terms only exist for
% the layers with leaves.
% ---------------------------------------------------------------------

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)

   if (surfvar.dpai(p,ic) > 0)

      % Calculate terms for sunlit and shaded leaves

      for il = 1:leafvar.nleaf

         % Leaf conductances (here these are per unit leaf area)

         gleaf_sh(ic,il) = 2 * leafvar.gbh(p,ic,il);
         gleaf = leafvar.gs(p,ic,il) * leafvar.gbv(p,ic,il) / (leafvar.gs(p,ic,il) + leafvar.gbv(p,ic,il));
         gleaf_et(ic,il) = gleaf * surfvar.fdry(p,ic) + leafvar.gbv(p,ic,il) * surfvar.fwet(p,ic);

         % Heat capacity of leaves

         heatcap(ic,il) = leafvar.cpleaf(p,ic);

         % Available energy: net radiation

         avail_energy(ic,il) = leafvar.rnleaf(p,ic,il);

         % Saturation vapor pressure and derivative for leaf temperature at time n: Pa -> mol/mol

         [esat, desat] = satvap (fluxvar.tveg_old(p,ic,il)-physcon.tfrz);
         qsat(ic,il) = esat / forcvar.pref(p);
         dqsat(ic,il) = desat / forcvar.pref(p);

         % Term for linearized vapor pressure at leaf temperature:
         % qsat(tveg) = qsat(tveg_old) + dqsat * (tveg - tveg_old)
         % Here qsat_term contains the terms with tveg_old

         qsat_term(ic,il) = qsat(ic,il) - dqsat(ic,il) * fluxvar.tveg_old(p,ic,il);

         % alpha, beta, delta coefficients for leaf temperature

         den = heatcap(ic,il) / dt + gleaf_sh(ic,il) * forcvar.cpair(p) + gleaf_et(ic,il) * lambda * dqsat(ic,il);
         alpha(ic,il) = gleaf_sh(ic,il) * forcvar.cpair(p) / den;
         beta(ic,il) = gleaf_et(ic,il) * lambda / den;
         delta(ic,il) = avail_energy(ic,il) / den ...
                      - lambda * gleaf_et(ic,il) * qsat_term(ic,il) / den ...
                      + heatcap(ic,il) / dt * fluxvar.tveg_old(p,ic,il) / den;

         % Now scale flux terms for plant area

         if (il == leafvar.isun)
            pai = surfvar.fracsun(p,ic) * surfvar.dpai(p,ic);
         elseif (il == leafvar.isha)
            pai = surfvar.fracsha(p,ic) * surfvar.dpai(p,ic);
         end

         gleaf_sh(ic,il) = gleaf_sh(ic,il) * pai;
         gleaf_et(ic,il) = gleaf_et(ic,il) * pai;
         heatcap(ic,il) = heatcap(ic,il) * pai;
         avail_energy(ic,il) = avail_energy(ic,il) * pai;

      end

   else

      % Zero out terms

      for il = 1:leafvar.nleaf
         gleaf_sh(ic,il) = 0;
         gleaf_et(ic,il) = 0;
         heatcap(ic,il) = 0;
         avail_energy(ic,il) = 0;
         qsat(ic,il) = 0;
         dqsat(ic,il) = 0;
         qsat_term(ic,il) = 0;
         alpha(ic,il) = 0;
         beta(ic,il) = 0;
         delta(ic,il) = 0;
      end

   end
end

% ---------------------------------------------------------------------
% a,b,c,d coefficients for air temperature:
% a1(i)*T(i-1) + b11(i)*T(i) + b12(i)*q(i) + c1(i)*T(i+1) = d1(i)
%
% a,b,c,d coefficients for water vapor (mole fraction):
% a2(i)*q(i-1) + b21(i)*T(i) + b22(i)*q(i) + c2(i)*q(i+1) = d2(i)
% ---------------------------------------------------------------------

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)

   % Storage term

   rho_dz_over_dt = forcvar.rhomol(p) * (surfvar.zw(p,ic) - surfvar.zw(p,ic-1)) / dt;

   % a1,b11,b12,c1,d1 coefficients for air temperature

   a1(ic) = -fluxvar.ga_prof(p,ic-1);
   b11(ic) = rho_dz_over_dt + fluxvar.ga_prof(p,ic-1) + fluxvar.ga_prof(p,ic) ...
           + gleaf_sh(ic,leafvar.isun) * (1 - alpha(ic,leafvar.isun)) ...
           + gleaf_sh(ic,leafvar.isha) * (1 - alpha(ic,leafvar.isha));
   b12(ic) = -gleaf_sh(ic,leafvar.isun) * beta(ic,leafvar.isun) - gleaf_sh(ic,leafvar.isha) * beta(ic,leafvar.isha);
   c1(ic) = -fluxvar.ga_prof(p,ic);
   d1(ic) = rho_dz_over_dt * fluxvar.tair_old(p,ic) + gleaf_sh(ic,leafvar.isun) * delta(ic,leafvar.isun) ...
          + gleaf_sh(ic,leafvar.isha) * delta(ic,leafvar.isha);

   % Special case for top layer

   if (ic == surfvar.nlev(p))
      c1(ic) = 0;
      d1(ic) = d1(ic) + fluxvar.ga_prof(p,ic) * forcvar.thref(p);
   end

   % Special case for first canopy layer (i.e., immediately above the ground)

   if (ic == surfvar.nsoi(p)+1)
      a1(ic) = 0;
      b11(ic) = b11(ic) - fluxvar.ga_prof(p,surfvar.nsoi(p)) * alpha0;
      b12(ic) = b12(ic) - fluxvar.ga_prof(p,surfvar.nsoi(p)) * beta0;
      d1(ic) = d1(ic) + fluxvar.ga_prof(p,surfvar.nsoi(p)) * delta0;
   end

   % a2,b21,b22,c2,d2 coefficients for water vapor (mole fraction)

   if (ic == surfvar.nsoi(p)+1)
      ga_prof_ic_minus_one = gs0;
   else
      ga_prof_ic_minus_one = fluxvar.ga_prof(p,ic-1);
   end

   a2(ic) = -ga_prof_ic_minus_one;
   b21(ic) = -gleaf_et(ic,leafvar.isun) * dqsat(ic,leafvar.isun) * alpha(ic,leafvar.isun) ...
             -gleaf_et(ic,leafvar.isha) * dqsat(ic,leafvar.isha) * alpha(ic,leafvar.isha);
   b22(ic) = rho_dz_over_dt + ga_prof_ic_minus_one + fluxvar.ga_prof(p,ic) ...
           + gleaf_et(ic,leafvar.isun) * (1 - dqsat(ic,leafvar.isun) * beta(ic,leafvar.isun)) ...
           + gleaf_et(ic,leafvar.isha) * (1 - dqsat(ic,leafvar.isha) * beta(ic,leafvar.isha));
   c2(ic) = -fluxvar.ga_prof(p,ic);
   d2(ic) = rho_dz_over_dt * fluxvar.qair_old(p,ic) ...
          + gleaf_et(ic,leafvar.isun) * (dqsat(ic,leafvar.isun) * delta(ic,leafvar.isun) + qsat_term(ic,leafvar.isun)) ...
          + gleaf_et(ic,leafvar.isha) * (dqsat(ic,leafvar.isha) * delta(ic,leafvar.isha) + qsat_term(ic,leafvar.isha));

   % Special case for top layer

   if (ic == surfvar.nlev(p))
      c2(ic) = 0;
      d2(ic) = d2(ic) + fluxvar.ga_prof(p,ic) * forcvar.qref(p);
   end

   % Special case for first canopy layer (i.e., immediately above the ground)

   if (ic == surfvar.nsoi(p)+1)
      a2(ic) = 0;
      b21(ic) = b21(ic) - gs0 * soilvar.rhg(p) * dqsat0 * alpha0;
      b22(ic) = b22(ic) - gs0 * soilvar.rhg(p) * dqsat0 * beta0;
      d2(ic) = d2(ic) + gs0 * soilvar.rhg(p) * (qsat0 + dqsat0 * (delta0 - fluxvar.tair_old(p,surfvar.nsoi(p))));
   end

end

% ---------------------------------------------------------------------
% Solve for air temperature and water vapor (mole fraction):
%
% a1(i)*T(i-1) + b11(i)*T(i) + b12(i)*q(i) + c1(i)*T(i+1) = d1(i)
% a2(i)*q(i-1) + b21(i)*T(i) + b22(i)*q(i) + c2(i)*q(i+1) = d2(i)
%
% The solution rewrites these equations so that:
% T(i) = f1(i) - e11(i)*T(i+1) - e12(i)*q(i+1) 
% q(i) = f2(i) - e21(i)*T(i+1) - e22(i)*q(i+1) 
% ---------------------------------------------------------------------

ic = surfvar.nsoi(p);
e11(ic) = 0;
e12(ic) = 0;
e21(ic) = 0;
e22(ic) = 0;
f1(ic) = 0;
f2(ic) = 0;

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)

   % The matrix to invert is:
   %
   % B(i)- A(i)*E(i-1)
   %
   % which is a 2x2 matrix. The
   % elements in the 2x2 matrix are: 
   %
   %                     | a b |
   % B(i)- A(i)*E(i-1) = | c d |
   %
   % Calculate these elements (denoted by ainv,binv,
   % cinv,dinv) and the determinant of the matrix.

   ainv = b11(ic) - a1(ic) * e11(ic-1);
   binv = b12(ic) - a1(ic) * e12(ic-1);
   cinv = b21(ic) - a2(ic) * e21(ic-1);
   dinv = b22(ic) - a2(ic) * e22(ic-1);
   det = ainv * dinv - binv * cinv;

   % E(i) = [B(i) - A(i)*E(i-1)]^(-1) * C(i)

   e11(ic) = dinv * c1(ic) / det;
   e12(ic) = -binv * c2(ic) / det;
   e21(ic) = -cinv * c1(ic) / det;
   e22(ic) = ainv * c2(ic) / det;

   % F(i) = [B(i) - A(i)*E(i-1)]^(-1) * [D(i) - A(i)*F(i-1)]

   f1(ic) =  (dinv*(d1(ic) - a1(ic)*f1(ic-1)) - binv*(d2(ic) - a2(ic)*f2(ic-1))) / det;
   f2(ic) = (-cinv*(d1(ic) - a1(ic)*f1(ic-1)) + ainv*(d2(ic) - a2(ic)*f2(ic-1))) / det;

end

% Top layer

ic = surfvar.nlev(p);
fluxvar.tair(p,ic) = f1(ic);
fluxvar.qair(p,ic) = f2(ic);

% Layers through to bottom of canopy

for ic = surfvar.nlev(p)-1: -1: surfvar.nsoi(p)+1
   fluxvar.tair(p,ic) = f1(ic) - e11(ic)*fluxvar.tair(p,ic+1) - e12(ic)*fluxvar.qair(p,ic+1);
   fluxvar.qair(p,ic) = f2(ic) - e21(ic)*fluxvar.tair(p,ic+1) - e22(ic)*fluxvar.qair(p,ic+1);
end

% Ground

ic = surfvar.nsoi(p);
fluxvar.tg(p) = alpha0 * fluxvar.tair(p,ic+1) + beta0 * fluxvar.qair(p,ic+1) + delta0;
fluxvar.tair(p,ic) = fluxvar.tg(p);
fluxvar.qair(p,ic) = soilvar.rhg(p) * (qsat0 + dqsat0 * (fluxvar.tair(p,ic) - fluxvar.tair_old(p,ic)));

% ---------------------------------------------------------------------
% Calculate leaf temperature:
% Tlsun(i) = alpha_sun(i)*T(i) + beta_sun(i)*q(i) + delta_sun(i)
% Tlsha(i) = alpha_sha(i)*T(i) + beta_sha(i)*q(i) + delta_sha(i)
% ---------------------------------------------------------------------

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)
   fluxvar.tveg(p,ic,leafvar.isun) = alpha(ic,leafvar.isun)*fluxvar.tair(p,ic) ...
                                   + beta(ic,leafvar.isun)*fluxvar.qair(p,ic) + delta(ic,leafvar.isun);
   fluxvar.tveg(p,ic,leafvar.isha) = alpha(ic,leafvar.isha)*fluxvar.tair(p,ic) ...
                                   + beta(ic,leafvar.isha)*fluxvar.qair(p,ic) + delta(ic,leafvar.isha);

   % Special checks for no plant area in layer

   if (surfvar.dpai(p,ic) > 0)
      pai = surfvar.fracsun(p,ic) * surfvar.dpai(p,ic);
      if (pai == 0)
         fluxvar.tveg(p,ic,leafvar.isun) = fluxvar.tair(p,ic);
      end
      pai = surfvar.fracsha(p,ic) * surfvar.dpai(p,ic);
      if (pai == 0)
         fluxvar.tveg(p,ic,leafvar.isha) = fluxvar.tair(p,ic);
      end
   else
      fluxvar.tveg(p,ic,leafvar.isun) = 0;
      fluxvar.tveg(p,ic,leafvar.isha) = 0;
   end
end

ic = surfvar.nsoi(p);
fluxvar.tveg(p,ic,leafvar.isun) = 0;
fluxvar.tveg(p,ic,leafvar.isha) = 0;

% --------------------
% Ground source fluxes
% --------------------

ic = surfvar.nsoi(p);
fluxvar.shsoi(p) = -forcvar.cpair(p) * (fluxvar.tair(p,ic+1) - fluxvar.tair(p,ic)) * fluxvar.ga_prof(p,ic);
fluxvar.etsoi(p) = -(fluxvar.qair(p,ic+1) - fluxvar.qair(p,ic)) * gs0;
fluxvar.gsoi(p) = c01 + c02 * fluxvar.tg(p);

% ------------------------
% Vegetation source fluxes
% ------------------------

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)
   fluxvar.shveg(p,ic) = 0;
   fluxvar.etveg(p,ic) = 0;
   fluxvar.stveg(p,ic) = 0;
   for il = 1:leafvar.nleaf
      fluxvar.shveg(p,ic) = fluxvar.shveg(p,ic) ...
                          + forcvar.cpair(p) * (fluxvar.tveg(p,ic,il) - fluxvar.tair(p,ic)) * gleaf_sh(ic,il);
      fluxvar.etveg(p,ic) = fluxvar.etveg(p,ic) ...
                          + (qsat(ic,il) + dqsat(ic,il) * (fluxvar.tveg(p,ic,il) - fluxvar.tveg_old(p,ic,il)) - fluxvar.qair(p,ic)) ...
                          * gleaf_et(ic,il);
      fluxvar.stveg(p,ic) = fluxvar.stveg(p,ic) ...
                          + heatcap(ic,il) * (fluxvar.tveg(p,ic,il) - fluxvar.tveg_old(p,ic,il)) / dt;
   end
end

% ------------------------------------------------------------
% Vertical sensible heat and water vapor fluxes between layers
% ------------------------------------------------------------

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)-1
   fluxvar.shair(p,ic) = -forcvar.cpair(p) * (fluxvar.tair(p,ic+1) - fluxvar.tair(p,ic)) * fluxvar.ga_prof(p,ic);
   fluxvar.etair(p,ic) = -(fluxvar.qair(p,ic+1) - fluxvar.qair(p,ic)) * fluxvar.ga_prof(p,ic);
end

ic = surfvar.nlev(p);
fluxvar.shair(p,ic) = -forcvar.cpair(p) * (forcvar.thref(p) - fluxvar.tair(p,ic)) * fluxvar.ga_prof(p,ic);
fluxvar.etair(p,ic) = -(forcvar.qref(p) - fluxvar.qair(p,ic)) * fluxvar.ga_prof(p,ic);

% --------------------------------------------------------------------------
% Canopy air storage flux (W/m2) and its sensible heat and water vapor terms
% --------------------------------------------------------------------------

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)
   dz_over_dt = (surfvar.zw(p,ic) - surfvar.zw(p,ic-1)) / dt;
   storage_sh(ic) = forcvar.rhomol(p) * forcvar.cpair(p) * (fluxvar.tair(p,ic) - fluxvar.tair_old(p,ic)) * dz_over_dt;
   storage_et(ic) = forcvar.rhomol(p) * (fluxvar.qair(p,ic) - fluxvar.qair_old(p,ic)) * dz_over_dt;
   fluxvar.stair(p,ic) = storage_sh(ic) + storage_et(ic) * lambda;
end

% -------------------
% Conservation checks
% -------------------

% Ground source fluxes energy balance

err = fluxvar.rnsoi(p) - fluxvar.shsoi(p) - lambda * fluxvar.etsoi(p) - fluxvar.gsoi(p);
if (abs(err) > 0.001)
   error ('ScalarProfile: Ground temperature energy balance error')
end

% Vegetation source fluxes energy balance

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)
   err = avail_energy(ic,leafvar.isun) + avail_energy(ic,leafvar.isha) ...
       - fluxvar.shveg(p,ic) - lambda * fluxvar.etveg(p,ic) - fluxvar.stveg(p,ic);
   if (abs(err) > 0.001)
      error ('ScalarProfile: Leaf temperature energy balance error')
   end
end

% Flux conservation at each layer. Note special case for first canopy layer.

for ic = surfvar.nsoi(p)+1:surfvar.nlev(p)

   if (ic == surfvar.nsoi(p)+1)
      err = storage_sh(ic) - (fluxvar.shsoi(p) + fluxvar.shveg(p,ic) - fluxvar.shair(p,ic));
   else
      err = storage_sh(ic) - (fluxvar.shair(p,ic-1) + fluxvar.shveg(p,ic) - fluxvar.shair(p,ic));
   end
   if (abs(err) > 0.001)
      error ('ScalarProfile: Sensible heat layer conservation error')
   end

   if (ic == surfvar.nsoi(p)+1)
      err = storage_et(ic) - (fluxvar.etsoi(p) + fluxvar.etveg(p,ic) - fluxvar.etair(p,ic));
   else
      err = storage_et(ic) - (fluxvar.etair(p,ic-1) + fluxvar.etveg(p,ic) - fluxvar.etair(p,ic));
   end
   err = err * lambda;
   if (abs(err) > 0.001)
      err ('ScalarProfile: Latent heat layer conservation error')
   end

end

% Flux conservation for canopy sensible heat. This is to check canopy
% conservation equation (so the sum is to ntop not nlev).

sum_src = fluxvar.shsoi(p);
sum_storage = 0;
for ic = surfvar.nsoi(p)+1:surfvar.ntop(p)
   sum_src = sum_src + fluxvar.shveg(p,ic);
   sum_storage = sum_storage + storage_sh(ic);
end

err = (sum_src - sum_storage) - fluxvar.shair(p,surfvar.ntop(p));
if (abs(err) > 0.001)
   error ('ScalarProfile: Sensible heat canopy conservation error')
end

% Flux conservation for canopy latent heat. This is to check canopy
% conservation equation (so the sum is to ntop not nlev).

sum_src = fluxvar.etsoi(p);
sum_storage = 0;
for ic = surfvar.nsoi(p)+1:surfvar.ntop(p)
   sum_src = sum_src + fluxvar.etveg(p,ic);
   sum_storage = sum_storage + storage_et(ic);
end

err = (sum_src - sum_storage) - fluxvar.etair(p,surfvar.ntop(p));
err = err * lambda;
if (abs(err) > 0.001)
   error ('ScalarProfile: Latent heat canopy conservation error')
end

Output

Figures

Figure 1

Text

sp_16_01_out.txt (standard output) | View on GitHub | View raw
0      0.000      0.000      0.000    294.843   2586.238      0.000      0.000
     1      0.250      0.000      0.100    295.656   1542.385      0.000      0.000
     2      0.750      0.000      0.100    295.748   1527.280      0.000      0.000
     3      1.250      0.000      0.100    295.807   1517.844      0.000      0.000
     4      1.750      0.000      0.100    295.846   1511.950      0.000      0.000
     5      2.250      0.000      0.100    295.872   1508.345      0.000      0.000
     6      2.750      0.010      0.100    295.890   1506.272    296.542    295.895
     7      3.250      0.015      0.100    295.903   1505.245    296.552    295.906
     8      3.750      0.021      0.100    295.912   1504.967    296.558    295.914
     9      4.250      0.028      0.100    295.918   1505.246    296.562    295.919
    10      4.750      0.036      0.100    295.923   1505.951    296.565    295.924
    11      5.250      0.045      0.100    295.926   1506.991    296.566    295.927
    12      5.750      0.054      0.100    295.929   1508.302    296.566    295.930
    13      6.250      0.064      0.100    295.931   1509.832    296.565    295.932
    14      6.750      0.075      0.100    295.933   1511.540    296.563    295.934
    15      7.250      0.087      0.100    295.934   1513.393    296.560    295.935
    16      7.750      0.099      0.100    295.934   1515.360    296.558    295.937
    17      8.250      0.112      0.100    295.935   1517.414    296.548    295.937
    18      8.750      0.124      0.100    295.934   1519.531    296.544    295.938
    19      9.250      0.137      0.100    295.934   1521.689    296.539    295.938
    20      9.750      0.149      0.100    295.933   1523.867    296.532    295.938
    21     10.250      0.162      0.100    295.931   1526.047    296.521    295.937
    22     10.750      0.174      0.114    295.929   1528.211    296.509    295.937
    23     11.250      0.185      0.130    295.926   1530.345    296.501    295.924
    24     11.750      0.196      0.148    295.924   1532.368    296.487    295.922
    25     12.250      0.206      0.169    295.922   1534.274    296.474    295.909
    26     12.750      0.214      0.192    295.921   1536.000    296.462    295.909
    27     13.250      0.222      0.219    295.920   1537.556    296.449    295.900
    28     13.750      0.227      0.250    295.921   1538.910    296.437    295.888
    29     14.250      0.231      0.285    295.923   1540.024    296.422    295.872
    30     14.750      0.234      0.325    295.927   1540.859    296.410    295.861
    31     15.250      0.234      0.371    295.933   1541.404    296.399    295.837
    32     15.750      0.231      0.422    295.941   1541.622    296.389    295.814
    33     16.250      0.226      0.482    295.951   1541.498    296.378    295.784
    34     16.750      0.218      0.549    295.964   1541.021    296.362    295.752
    35     17.250      0.207      0.626    295.978   1540.197    296.349    295.712
    36     17.750      0.193      0.714    295.994   1539.045    296.335    295.662
    37     18.250      0.175      0.814    296.011   1537.598    296.317    295.614
    38     18.750      0.153      0.928    296.027   1535.918    296.301    295.558
    39     19.250      0.127      1.058    296.043   1534.080    296.280    295.507
    40     19.750      0.097      1.207    296.057   1532.174    296.264    295.470
    41     20.250      0.062      1.376    296.069   1530.293    296.265    295.457
    42     20.750      0.022      1.569    296.079   1528.527    296.364    295.550
    43     21.250      0.000      1.780    296.088   1526.948      0.000      0.000
    44     21.750      0.000      1.964    296.096   1525.528      0.000      0.000
    45     22.250      0.000      2.124    296.104   1524.219      0.000      0.000
    46     22.750      0.000      2.268    296.111   1522.996      0.000      0.000
    47     23.250      0.000      2.398    296.119   1521.844      0.000      0.000
    48     23.750      0.000      2.518    296.126   1520.752      0.000      0.000
    49     24.250      0.000      2.629    296.132   1519.712      0.000      0.000
    50     24.750      0.000      2.734    296.139   1518.717      0.000      0.000
    51     25.250      0.000      2.833    296.145   1517.764      0.000      0.000
    52     25.750      0.000      2.927    296.152   1516.848      0.000      0.000
    53     26.250      0.000      3.017    296.158   1515.966      0.000      0.000
    54     26.750      0.000      3.103    296.164   1515.116      0.000      0.000
    55     27.250      0.000      3.185    296.170   1514.295      0.000      0.000
    56     27.750      0.000      3.264    296.177   1513.501      0.000      0.000
    57     28.250      0.000      3.341    296.183   1512.732      0.000      0.000
    58     28.750      0.000      3.414    296.188   1511.986      0.000      0.000
    59     29.250      0.000      3.486    296.194   1511.263      0.000      0.000
    60     29.750      0.000      3.555    296.200   1510.561      0.000      0.000
    61     30.250      0.000      3.623    296.206   1509.878      0.000      0.000
    62     30.750      0.000      3.688    296.212   1509.214      0.000      0.000
    63     31.250      0.000      3.752    296.218   1508.568      0.000      0.000
    64     31.750      0.000      3.814    296.223   1507.938      0.000      0.000
    65     32.250      0.000      3.875    296.229   1507.323      0.000      0.000
    66     32.750      0.000      3.934    296.235   1506.724      0.000      0.000
    67     33.250      0.000      3.992    296.241   1506.139      0.000      0.000
    68     33.750      0.000      4.049    296.246   1505.567      0.000      0.000
    69     34.250      0.000      4.104    296.252   1505.009      0.000      0.000
    70     34.750      0.000      4.159    296.257   1504.462      0.000      0.000
    71     35.250      0.000      4.212    296.263   1503.927      0.000      0.000
    72     35.750      0.000      4.264    296.269   1503.403      0.000      0.000
    73     36.250      0.000      4.315    296.274   1502.889      0.000      0.000
    74     36.750      0.000      4.366    296.280   1502.386      0.000      0.000
    75     37.250      0.000      4.415    296.286   1501.892      0.000      0.000
    76     37.750      0.000      4.464    296.291   1501.407      0.000      0.000
    77     38.250      0.000      4.512    296.297   1500.931      0.000      0.000
    78     38.750      0.000      4.559    296.303   1500.464      0.000      0.000
    79     39.250      0.000      4.605    296.308   1500.004      0.000      0.000
    80     39.750      0.000      4.650    296.314   1499.552      0.000      0.000
    81     40.250      0.000      4.695    296.320   1499.107      0.000      0.000
    82     40.750      0.000      4.739    296.325   1498.669      0.000      0.000
    83     41.250      0.000      4.783    296.331   1498.238      0.000      0.000
    84     41.750      0.000      4.826    296.337   1497.813      0.000      0.000
    85     42.250      0.000      4.868    296.342   1497.394      0.000      0.000
    86     42.750      0.000      4.910    296.348   1496.981      0.000      0.000
    87     43.250      0.000      4.951    296.354   1496.574      0.000      0.000
    88     43.750      0.000      4.992    296.360   1496.171      0.000      0.000
    89     44.250      0.000      5.032    296.365   1495.774      0.000      0.000
    90     44.750      0.000      5.072    296.371   1495.382      0.000      0.000
    91     45.250      0.000      5.111    296.377   1494.994      0.000      0.000
    92     45.750      0.000      5.150    296.383   1494.611      0.000      0.000