double operator() (double value) const { return pF2h (value); }
void Horizon::initialize_base (const bool top_soil, const int som_size, const double center_z, const Texture& texture, Treelog& msg) { TREELOG_MODEL (msg); const double clay_lim = texture_below ( 2.0 /* [um] USDA Clay */); fast_clay = texture.mineral () * clay_lim; fast_humus = texture.humus; impl->initialize (*hydraulic, texture, quartz () * texture.mineral (), som_size, top_soil, center_z, msg); impl->secondary->initialize (msg); std::ostringstream tmp; const double h_lim = secondary_domain ().h_lim (); if (h_lim >= 0.0) impl->primary_sorption_fraction = 1.0; else { const double h_sat = 0.0; const double Theta_sat = hydraulic->Theta (h_sat); // Integrate h over Theta. PLF h_int; const double h_min = Hydraulic::r2h (impl->r_pore_min); const double Theta_min =hydraulic->Theta (h_min); h_int.add (Theta_min, 0.0); static const int intervals = 100; double delta = (Theta_sat - Theta_min) / (intervals + 0.0); double sum = 0.0; for (double Theta = Theta_min + delta; Theta < Theta_sat; Theta += delta) { double my_h = hydraulic->h (Theta); sum += my_h * delta; h_int.add (Theta, sum); } const double h_wp = -15000.0; const double Theta_wp = hydraulic->Theta (h_wp); const double Theta_lim = hydraulic->Theta (h_lim); tmp << "A saturated secondary domain contain " << 100.0 * (Theta_sat - Theta_lim) / (Theta_sat - Theta_wp) << " % of plant available water\n"; impl->primary_sorption_fraction = h_int (Theta_lim) / h_int (Theta_sat); tmp << "Primary domain contains " << 100.0 * impl->primary_sorption_fraction << " % of the available sorption sites\n"; } tmp << "h\th\tTheta\tK\n" << "cm\tpF\t\tcm/h\n"; const double h_Sat = 0; tmp << h_Sat << "\t" << "\t" << hydraulic->Theta (h_Sat) << "\t" << hydraulic->K (h_Sat) << "\n"; const double pF_Zero = 0; const double h_Zero = pF2h (pF_Zero); tmp << h_Zero << "\t" << pF_Zero << "\t" << hydraulic->Theta (h_Zero) << "\t" << hydraulic->K (h_Zero) << "\n"; const double pF_One = 1; const double h_One = pF2h (pF_One); tmp << h_One << "\t" << pF_One << "\t" << hydraulic->Theta (h_One) << "\t" << hydraulic->K (h_One) << "\n"; const double pF_FC = 2.0; const double h_FC = pF2h (pF_FC); tmp << h_FC << "\t" << pF_FC << "\t" << hydraulic->Theta (h_FC) << "\t" << hydraulic->K (h_FC) << "\n"; const double pF_Three = 3; const double h_Three = pF2h (pF_Three); tmp << h_Three << "\t" << pF_Three << "\t" << hydraulic->Theta (h_Three) << "\t" << hydraulic->K (h_Three) << "\n"; const double pF_WP = 4.2; const double h_WP = pF2h (pF_WP); tmp << h_WP << "\t" << pF_WP << "\t" << hydraulic->Theta (h_WP) << "\t" << hydraulic->K (h_WP); msg.debug (tmp.str ()); }
void UZlr::tick (Treelog& msg, const GeometryVert& geo, const Soil& soil, const SoilHeat& soil_heat, unsigned int first, const Surface& top, const size_t top_edge, unsigned int last, const Groundwater& bottom, const size_t bottom_edge, const std::vector<double>& S, const std::vector<double>& h_old, const std::vector<double>& Theta_old, const std::vector<double>& h_ice, std::vector<double>& h, std::vector<double>& Theta, const size_t q_offset, std::vector<double>& q_base, const double dt) { double *const q = &q_base[q_offset]; double q_up = 0.0; double q_down = 0.0; const Surface::top_t top_type = top.top_type (geo, top_edge); if (top_type == Surface::soil) { // We have a forced pressure top, in the form of a ridge system. // Since LR only works with flux top, we use Darcy to simulate a // flux top between the first cell (with a forced pressure) and // the second cell, and then continue calculating with a flux // top from the second cell. const double dz = geo.cell_z (first) - geo.cell_z (first+1); const double dh = (h_old[first] - h_old[first+1]); const double K = std::min (soil.K (first, h_old[first], h_ice[first], soil_heat.T (first)), soil.K (first, h_old[first+1], h_ice[first+1], soil_heat.T (first+1))); q_up = -K * (dh/dz + 1.0); // We can safely ignore S[first], since the ridge system has // already incorporated it. first++; // New upper limit. q[first] = q_up; } else { // Limit flux by soil capacity. const double K_sat = soil.K (first, 0.0, h_ice[first], soil_heat.T (first)); daisy_assert (K_sat > 0.0); if (top_type == Surface::forced_pressure) { const double dz = 0.0 - geo.cell_z (first); const double dh = top.h_top (geo, top_edge) - h_old[first]; q_up = q[first] = -K_sat * (dh/dz + 1.0); } else // Limited water or forced flux. q_up = q[first] = std::max (top.q_top (geo, top_edge, dt), -K_sat); } // Use darcy for upward movement in the top. const bool use_darcy = (h_old[first] < h_fc) && (q_up > 0.0); // Intermediate cells. for (int i = first; i <= last; i++) { const double z = geo.cell_z (i); const double dz = geo.dz (i); const double Theta_sat = soil.Theta (i, 0.0, h_ice[i]); const double Theta_res = soil.Theta_res (i); const double h_min = pF2h (10.0); const double Theta_min = soil.Theta (i, h_min, h_ice[i]); double Theta_new = Theta_old[i] - q[i] * dt / dz - S[i] * dt; if (Theta_new < Theta_min) { // Extreme dryness. q[i+1] = (Theta_min - Theta_new) * dz / dt; Theta[i] = Theta_min; h[i] = h_min; daisy_assert (std::isfinite (h[i])); continue; } daisy_assert (std::isfinite (h_old[i])); const double h_new = Theta_new >= Theta_sat ? std::max (h_old[i], 0.0) : soil.h (i, Theta_new); daisy_assert (std::isfinite (h_new)); double K_new = soil.K (i, h_new, h_ice[i], soil_heat.T (i)); // If we have free drainage bottom, we go for field capacity all // the way. Otherwise, we assume groundwater start at the // bottom of the last cell, and attempt equilibrium from there. // This asumption is correct for lysimeter bottom, adequate for // pressure bottom (where groundwater table is in the last // cell), and wrong for forced flux (= pipe drained soil) where // the groundwater is usually much higher. Still, it is better // than using h_fc. double h_lim; switch (bottom.bottom_type ()) { case Groundwater::free_drainage: h_lim = h_fc; break; case Groundwater::pressure: h_lim = std::max (bottom.table () - z, h_fc); break; case Groundwater::lysimeter: default: h_lim = std::max (geo.zplus (last) - z, h_fc); break; } if (use_darcy && z > z_top && i < last && h_fc < h_ice[i] ) // Dry earth, near top. Use darcy to move water up. { const double dist = z - geo.cell_z (i+1); q[i+1] = std::max (K_new * ((h_old[i+1] - h_new) / dist - 1.0), 0.0); const double Theta_next = Theta_new + q[i+1] * dt / dz; if (Theta_next > Theta_sat) { q[i+1] = (Theta_sat - Theta_new) * dz / dt; Theta[i] = Theta_sat; h[i] = std::max (0.0, h_new); daisy_assert (std::isfinite (h[i])); } else { Theta[i] = Theta_next; h[i] = soil.h (i, Theta[i]); daisy_assert (std::isfinite (h[i])); } } else if (h_new <= h_lim) // Dry earth, no water movement. { if (Theta_new <= Theta_sat) { q[i+1] = 0.0; Theta[i] = Theta_new; h[i] = h_new; daisy_assert (std::isfinite (h[i])); } else { q[i+1] = (Theta_sat - Theta_new) * dz / dt; Theta[i] = Theta_sat; h[i] = std::max (0.0, h_new); daisy_assert (std::isfinite (h[i])); } } else // Gravitational water movement. { if (i < last) { // Geometric average K. if (h_ice[i+1] < h_fc) // Blocked by ice. K_new = 0.0; else K_new = sqrt (K_new * soil.K (i+1, h_old[i+1], h_ice[i+1], soil_heat.T (i+1))); } else if (bottom.bottom_type () == Groundwater::forced_flux) K_new = -bottom.q_bottom (bottom_edge); // else keep K_new from the node. const double Theta_lim = soil.Theta (i, h_lim, h_ice[i]); const double Theta_next = Theta_new - K_new * dt / dz; if (Theta_next < Theta_lim) { if (Theta_lim < Theta_new) { q[i+1] = (Theta_lim - Theta_new) * dz / dt; Theta[i] = Theta_lim; h[i] = h_lim; daisy_assert (std::isfinite (h[i])); } else { q[i+1] = 0.0; Theta[i] = Theta_new; h[i] = h_new; daisy_assert (std::isfinite (h[i])); } } else if (Theta_next >= Theta_sat) { q[i+1] = (Theta_sat - Theta_new) * dz / dt; Theta[i] = Theta_sat; h[i] = std::max (h_old[i], 0.0); daisy_assert (std::isfinite (h[i])); } else { q[i+1] = -K_new; Theta[i] = Theta_next; h[i] = soil.h (i, Theta[i]); daisy_assert (std::isfinite (h[i])); } } daisy_assert (std::isfinite (h[i])); daisy_assert (std::isfinite (Theta[i])); daisy_assert (std::isfinite (q[i+1])); daisy_assert (Theta[i] <= Theta_sat); daisy_assert (Theta[i] > Theta_res); } // Lower border. q_down = q[last + 1]; if (bottom.bottom_type () == Groundwater::forced_flux) // Ensure forced bottom. { double extra_water = (bottom.q_bottom (bottom_edge) - q_down) * dt; for (int i = last; true; i--) { q[i+1] += extra_water / dt; if (i < static_cast<int> (first)) { if (extra_water > 0.0 && overflow_warn) { msg.warning ("Soil profile saturated, water flow to surface"); overflow_warn = false; } break; } const double dz = geo.dz (i); const double h_min = pF2h (10.0); const double Theta_min = soil.Theta (i, h_min, h_ice[i]); const double Theta_sat = soil.Theta (i, 0.0, h_ice[i]); Theta[i] += extra_water / dz; if (Theta[i] <= Theta_min) { extra_water = (Theta[i] - Theta_min) * dz; Theta[i] = Theta_min; h[i] = h_min; } else if (Theta[i] <= Theta_sat) { extra_water = 0; h[i] = soil.h (i, Theta[i]); break; } else { extra_water = (Theta[i] - Theta_sat) * dz; Theta[i] = Theta_sat; h[i] = 0.0; } } q_up = q[first]; q_down = q[last + 1]; } // Saturated pressure. double table = geo.cell_z (last) + h[last]; for (int i = last; i > first; i--) if (h[i] < 0.0) { table = geo.cell_z (i) + h[i]; break; } for (int i = last; i > first; i--) if (geo.cell_z (i) < table) { daisy_assert (h[i] >= 0.0); h[i] = table - geo.cell_z (i); daisy_assert (h[i] >= 0.0); } else break; // Check mass conservation. double total_old = 0.0; double total_new = 0.0; double total_S = 0.0; for (unsigned int i = first; i <= last; i++) { const double Theta_sat = soil.Theta (i, 0.0, 0.0); daisy_assert (Theta[i] <= Theta_sat + 1e-10); total_old += geo.dz (i) * Theta_old[i]; total_new += geo.dz (i) * Theta[i]; total_S += geo.dz (i) * S[i]; daisy_assert (std::isfinite (Theta[i])); if (Theta[i] <= 0.0) { std::ostringstream tmp; tmp << "Theta[" << i << "] = " << Theta[i]; daisy_bug (tmp.str ()); Theta[i] = 1e-9; } } daisy_balance (total_old, total_new, (-q_up + q_down - total_S) * dt); }