diff --git a/docs/src/ptf-catalog/sources/index.md b/docs/src/ptf-catalog/sources/index.md index ab123fb..d661714 100644 --- a/docs/src/ptf-catalog/sources/index.md +++ b/docs/src/ptf-catalog/sources/index.md @@ -21,9 +21,11 @@ Each page describes the source, scope, inputs, outputs, status, and limitations | [Jabro (1992), United States.](./jabro1992.md) | USA | 1 | | [Li et al. (2007), Fengqiu County, North China Plain, China.](./li2007.md) | Fengqiu County soils in the North China Plain, China | 1 | | [Mayr and Jarvis (1999) modified Brooks-Corey water-retention parameter PTFs.](./mayr1999.md) | England and Wales | 1 | +| [Oosterveld and Chang (1980), soil-moisture retention from texture and depth.](./oosterveld1980.md) | Southern Alberta, Canada | 5 | | [Pidgeon (1972), available-water regressions for ferrallitic soils in Uganda.](./pidgeon1972.md) | Non-alluvial ferrallitic soils in Uganda, predominantly kaolinitic and possibly illitic | 17 | | [Puckett et al. (1985), Alabama Lower Coastal Plain Ultisols.](./puckett1985.md) | Lower Coastal Plain of Alabama, USA | 1 | | [Rawls et al. (1982), water-retention regressions for soils across the USA.](./rawls1982.md) | Agricultural soils from 32 states of the USA | 3 | +| [Saxton and Rawls (2006), soil water characteristics from texture and organic matter.](./saxton2006.md) | United States | 7 | | [Tiwary et al. (2014), hydraulic PTFs for two major soil regions of India.](./tiwary2014.md) | Indo-Gangetic Plains and black soil region of India | 2 | | [Varallyai et al. (1982), water-retention parameter regressions for Hungarian soils.](./varallyai1982.md) | Hungary, mainly the Hungarian Plain | 4 | | [Vereecken et al. soil-moisture retention regressions for Belgian soils.](./vereecken1989.md) | Belgian territory north of the river axis Samber and Meuse | 2 | diff --git a/docs/src/ptf-catalog/sources/oosterveld1980.md b/docs/src/ptf-catalog/sources/oosterveld1980.md new file mode 100644 index 0000000..e597f16 --- /dev/null +++ b/docs/src/ptf-catalog/sources/oosterveld1980.md @@ -0,0 +1,167 @@ +--- +# @generated by ptfkit-codegen; DO NOT EDIT. + +title: PTF source oosterveld1980 +nav-title: oosterveld1980 +--- + +# Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + +## Source + +Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory determinations of soil texture and moisture retention. Canadian Agricultural Engineering, 22, 149-151. + +## Scope + +**Territory:** Southern Alberta, Canada + +**Dataset:** 1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research Station laboratory; the field-capacity tension regression used 134 samples. + +## Functions + +### `calc_ptf_oosterveld1980_field_capacity_tension` + +Estimate field-capacity tension from clay content. + +**Status:** `implemented` + +**Prediction target:** Tension at which pressure-plate moisture content equals field capacity determined by the cylinder method. + +**Models:** $h(\theta)$ — Field-capacity tension point estimate + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `clay` | % mass | 2 <= value <= 72 | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `field_capacity_tension` | kPa | value > 0 | Predicted tension corresponding to cylinder-method field capacity. | + +!!! note + + Equation 1 reports r = 0.67 and n = 134. + +### `calc_ptf_oosterveld1980_retention` + +Estimate gravimetric soil-moisture content from texture, depth, and tension. + +**Status:** `implemented` + +**Prediction target:** Gravimetric soil-moisture content at a specified tension. + +**Models:** $h(\theta)$ — Empirical power-law moisture-retention relation + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `clay` | % mass | — | Clay content in percent by weight. | +| `sand` | % mass | — | Sand content in percent by weight. | +| `mean_depth` | cm | 8 <= value <= 180 | Mean depth of the soil sample. | +| `tension` | kPa | 10 <= value <= 1500 | Soil-moisture tension. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `moisture_content` | % mass | — | Predicted soil-moisture content in percent by weight. | + +!!! note + + Equation 2 reports r = 0.96 and n = 1,137. + +!!! warning + + The paper reports slight inaccuracy at very high clay content and high tension. + +### `calc_ptf_oosterveld1980_field_capacity` + +Estimate gravimetric soil-moisture content at field capacity. + +**Status:** `implemented` + +**Prediction target:** Gravimetric soil-moisture content at field capacity. + +**Models:** $h(\theta)$ — Field-capacity point estimate derived from Equations 1 and 2 + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `clay` | % mass | 2 <= value <= 72 | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. | +| `sand` | % mass | — | Sand content in percent by weight. | +| `mean_depth` | cm | 8 <= value <= 180 | Mean depth of the soil sample. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `field_capacity_moisture` | % mass | — | Predicted soil-moisture content at field capacity in percent by weight. | + +!!! note + + Equation 3 is obtained by substituting the Equation 1 field-capacity tension into Equation 2. + +### `calc_ptf_oosterveld1980_wilting_point` + +Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + +**Status:** `implemented` + +**Prediction target:** Gravimetric soil-moisture content at 1500 kPa. + +**Models:** $h(\theta)$ — Wilting-point estimate at 1500 kPa + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `clay` | % mass | — | Clay content in percent by weight. | +| `sand` | % mass | — | Sand content in percent by weight. | +| `mean_depth` | cm | 8 <= value <= 180 | Mean depth of the soil sample. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `wilting_point_moisture` | % mass | — | Predicted soil-moisture content at 1500 kPa in percent by weight. | + +!!! note + + Equation 4 reports r = 0.96 and n = 298. + +!!! note + + The paper takes moisture content at 1500 kPa as the wilting point. + +### `calc_ptf_oosterveld1980_available_water` + +Estimate available gravimetric soil moisture between field capacity and wilting point. + +**Status:** `implemented` + +**Prediction target:** Difference between gravimetric moisture content at field capacity and at the 1500 kPa wilting point. + +**Models:** $h(\theta)$ — Available-moisture point estimate from Equations 3 and 4 + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `clay` | % mass | 2 <= value <= 72 | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. | +| `sand` | % mass | — | Sand content in percent by weight. | +| `mean_depth` | cm | 8 <= value <= 180 | Mean depth of the soil sample. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `available_moisture` | % mass | — | Predicted field-capacity moisture minus predicted wilting-point moisture, in percentage points by weight. | + +!!! note + + The paper defines available soil water by subtracting Equation 4 from Equation 3. diff --git a/docs/src/ptf-catalog/sources/saxton2006.md b/docs/src/ptf-catalog/sources/saxton2006.md new file mode 100644 index 0000000..fde6aed --- /dev/null +++ b/docs/src/ptf-catalog/sources/saxton2006.md @@ -0,0 +1,274 @@ +--- +# @generated by ptfkit-codegen; DO NOT EDIT. + +title: PTF source saxton2006 +nav-title: saxton2006 +--- + +# Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + +## Source + +Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic estimates by texture and organic matter for hydrologic solutions. Soil Science Society of America Journal, 70(5), 1569-1578. + +[DOI: 10.2136/ssaj2005.0117](https://doi.org/10.2136/ssaj2005.0117) + +## Scope + +**Territory:** United States + +**Dataset:** 1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National Soil Characterization database after exclusions from 2,149 samples. + +## Functions + +### `calc_ptf_saxton2006` + +Estimate soil water characteristics from sand, clay, and organic matter. + +**Status:** `implemented` + +**Prediction target:** Water contents at 1500, 33, and 0 kPa, plant-available water, air-entry tension, retention coefficients, normal density, and saturated hydraulic conductivity. + +**Models:** $h(\theta)$ — Campbell power function with a linear wet-range segment; $k(h)$ — Campbell unsaturated conductivity model + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `sand` | g/g | 0 <= value <= 1 | Sand mass fraction of the fine-earth soil. | +| `clay` | g/g | 0 <= value <= 0.60 | Clay mass fraction of the fine-earth soil. | +| `organic_matter` | % mass | 0 <= value <= 8 | Organic matter content on a mass percentage basis. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `theta_1500` | m^3/m^3 | value > 0 | Volumetric water content at 1500 kPa matric tension. | +| `theta_33` | m^3/m^3 | value > 0 | Volumetric water content at 33 kPa matric tension. | +| `theta_s` | m^3/m^3 | value > 0 | Saturated volumetric water content at normal density. | +| `plant_available_water` | m^3/m^3 | value >= 0 | Difference between the 33 and 1500 kPa water contents. | +| `air_entry_tension` | kPa | value > 0 | Air-entry or bubbling-pressure tension. | +| `retention_a` | kPa | value > 0 | Coefficient A of the dry-range moisture-tension curve. | +| `retention_b` | dimensionless | value > 0 | Exponent B of the dry-range moisture-tension curve. | +| `conductivity_lambda` | dimensionless | value > 0 | Inverse of retention exponent B. | +| `saturated_conductivity` | mm/h | value >= 0 | Saturated hydraulic conductivity of the matric soil. | +| `normal_density` | g/cm^3 | value > 0 | Normal dry bulk density assuming particle density 2.65 g/cm^3. | + +!!! note + + Sand and clay are decimal mass fractions, while organic matter is a percentage. + +!!! note + + The 1500 and 33 kPa values are also termed wilting point and field capacity. + +!!! warning + + Do not apply the regression above 8% organic matter or 60% clay. + +!!! warning + + Sand and clay fractions must describe one soil and therefore must sum to at most 1. + +!!! warning + + These statistical-average estimates should be calibrated to local measurements when available. + +### `calc_density_adjustment_saxton2006` + +Adjust Saxton and Rawls water characteristics for soil density. + +**Status:** `implemented` + +**Prediction target:** Density-adjusted saturation and 33 kPa water contents. + +**Models:** $h(\theta)$ — Density adjustment to the point estimates + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `normal_density` | g/cm^3 | value > 0 | Normal dry bulk density estimated by the base model. | +| `theta_s` | m^3/m^3 | value > 0 | Saturated water content at normal density. | +| `theta_33` | m^3/m^3 | value > 0 | Water content at 33 kPa and normal density. | +| `density_factor` | dimensionless | 0.9 <= value <= 1.3 | Multiplicative adjustment to normal density. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `adjusted_density` | g/cm^3 | value > 0 | Density after applying the density factor. | +| `adjusted_theta_s` | m^3/m^3 | value > 0 | Saturated water content after the density adjustment. | +| `adjusted_theta_33` | m^3/m^3 | value > 0 | Water content at 33 kPa after the density adjustment. | +| `adjusted_theta_s_minus_33` | m^3/m^3 | value >= 0.005 | Density-adjusted difference between saturation and 33 kPa. | + +!!! note + + The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + +!!! warning + + The source recommends density factors only from 0.9 to 1.3. + +### `calc_tension_dry_saxton2006` + +Estimate matric tension in the 1500 to 33 kPa segment. + +**Status:** `implemented` + +**Prediction target:** Matric tension for water content between theta_1500 and theta_33. + +**Models:** $h(\theta)$ — Campbell power function + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `theta` | m^3/m^3 | theta_1500 <= value <= theta_33 | Volumetric water content. | +| `theta_1500` | m^3/m^3 | value > 0 | Volumetric water content at 1500 kPa. | +| `theta_33` | m^3/m^3 | value > theta_1500 | Volumetric water content at 33 kPa. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `tension` | kPa | 33 <= value <= 1500 | Matric tension at the supplied water content. | + +!!! warning + + Use only for the 1500 to 33 kPa segment defined by the source. + +### `calc_tension_wet_saxton2006` + +Estimate matric tension in the 33 kPa to air-entry segment. + +**Status:** `implemented` + +**Prediction target:** Matric tension for water content between theta_33 and theta_s. + +**Models:** $h(\theta)$ — Linear wet-range segment + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `theta` | m^3/m^3 | theta_33 <= value <= theta_s | Volumetric water content. | +| `theta_33` | m^3/m^3 | value > 0 | Volumetric water content at 33 kPa. | +| `theta_s` | m^3/m^3 | value > theta_33 | Saturated volumetric water content. | +| `air_entry_tension` | kPa | 0 <= value < 33 | Air-entry tension estimated by the base model. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `tension` | kPa | air_entry_tension <= value <= 33 | Matric tension at the supplied water content. | + +!!! note + + At tensions below air entry, Equation 13 fixes water content at theta_s. + +!!! warning + + Use only for the 33 kPa to air-entry segment defined by the source. + +### `calc_conductivity_saxton2006` + +Estimate unsaturated hydraulic conductivity from water content. + +**Status:** `implemented` + +**Prediction target:** Unsaturated hydraulic conductivity of the matric soil. + +**Models:** $k(h)$ — Campbell unsaturated conductivity model + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `theta` | m^3/m^3 | 0 < value <= theta_s | Volumetric water content. | +| `theta_s` | m^3/m^3 | value > 0 | Saturated volumetric water content. | +| `saturated_conductivity` | mm/h | value >= 0 | Saturated hydraulic conductivity of the matric soil. | +| `conductivity_lambda` | dimensionless | value > 0 | Inverse of retention exponent B. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `conductivity` | mm/h | value >= 0 | Unsaturated hydraulic conductivity at the supplied water content. | + +!!! warning + + The equation does not include residual water content. + +### `calc_gravel_adjustment_saxton2006` + +Adjust matric-soil properties for gravel content. + +**Status:** `implemented` + +**Prediction target:** Gravel volume, bulk density, available water, and saturated conductivity. + +**Models:** $h(\theta)$ — Gravel reduction of plant-available water; $k(h)$ — Peck-Watson conductivity reduction + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `gravel_weight_fraction` | g/g | 0 <= value < 1 | Gravel mass fraction of the bulk soil. | +| `matric_density` | g/cm^3 | 0 < value <= 2.65 | Dry bulk density of the fine-earth matric soil. | +| `plant_available_water` | m^3/m^3 | value >= 0 | Plant-available water of the matric soil. | +| `saturated_conductivity` | mm/h | value >= 0 | Saturated hydraulic conductivity of the matric soil. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `gravel_volume_fraction` | m^3/m^3 | 0 <= value < 1 | Gravel volume fraction of the bulk soil. | +| `bulk_density` | g/cm^3 | value > 0 | Dry bulk density of matric soil plus gravel. | +| `bulk_plant_available_water` | m^3/m^3 | value >= 0 | Plant-available water on a bulk-soil volume basis. | +| `bulk_saturated_conductivity` | mm/h | value >= 0 | Saturated conductivity after the gravel reduction. | + +!!! note + + Gravel comprises particles larger than 2 mm. + +!!! note + + Gravel density is fixed at 2.65 g/cm^3 in the source equations. + +!!! warning + + The conductivity correction does not represent extra macropores sometimes found in gravelly soils. + +### `calc_osmotic_potential_saxton2006` + +Estimate saturated and moisture-adjusted osmotic potential. + +**Status:** `implemented` + +**Prediction target:** Osmotic potential from saturated-extract electrical conductivity. + +**Models:** $h(\theta)$ — Matric-plus-osmotic tension adjustment + +#### Inputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `electrical_conductivity` | dS/m | value >= 0 | Electrical conductivity of a saturated soil extract. | +| `theta` | m^3/m^3 | 0 < value <= theta_s | Current volumetric water content. | +| `theta_s` | m^3/m^3 | value > 0 | Saturated volumetric water content. | + +#### Outputs + +| Name | Unit | Domain | Description | +| --- | --- | --- | --- | +| `saturated_osmotic_potential` | kPa | value >= 0 | Osmotic potential at saturation. | +| `osmotic_potential` | kPa | value >= 0 | Osmotic potential at the supplied water content. | + +!!! note + + The moisture adjustment assumes dissolved salt quantity remains near constant as water content falls. + +!!! warning + + Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity effects. diff --git a/docs/src/reference/c/functions.md b/docs/src/reference/c/functions.md index 9250107..feea01a 100644 --- a/docs/src/reference/c/functions.md +++ b/docs/src/reference/c/functions.md @@ -78,6 +78,11 @@ title: C function index | [`calc_ptf_jabro1992`](headers/jabro1992.md#function-calc_ptf_jabro1992) | Estimate saturated hydraulic conductivity from silt, clay, and bulk density. | [``](headers/jabro1992.md) | | [`calc_ptf_li2007`](headers/li2007.md#function-calc_ptf_li2007) | Estimate van Genuchten parameters and saturated hydraulic conductivity for Fengqiu County soils. | [``](headers/li2007.md) | | [`calc_ptf_mayr1999`](headers/mayr1999.md#function-calc_ptf_mayr1999) | Estimate modified Brooks-Corey a, b, and saturated water content from texture, bulk density, and organic carbon. | [``](headers/mayr1999.md) | +| [`calc_ptf_oosterveld1980_available_water`](headers/oosterveld1980.md#function-calc_ptf_oosterveld1980_available_water) | Estimate available gravimetric soil moisture between field capacity and wilting point. | [``](headers/oosterveld1980.md) | +| [`calc_ptf_oosterveld1980_field_capacity`](headers/oosterveld1980.md#function-calc_ptf_oosterveld1980_field_capacity) | Estimate gravimetric soil-moisture content at field capacity. | [``](headers/oosterveld1980.md) | +| [`calc_ptf_oosterveld1980_field_capacity_tension`](headers/oosterveld1980.md#function-calc_ptf_oosterveld1980_field_capacity_tension) | Estimate field-capacity tension from clay content. | [``](headers/oosterveld1980.md) | +| [`calc_ptf_oosterveld1980_retention`](headers/oosterveld1980.md#function-calc_ptf_oosterveld1980_retention) | Estimate gravimetric soil-moisture content from texture, depth, and tension. | [``](headers/oosterveld1980.md) | +| [`calc_ptf_oosterveld1980_wilting_point`](headers/oosterveld1980.md#function-calc_ptf_oosterveld1980_wilting_point) | Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. | [``](headers/oosterveld1980.md) | | [`calc_ptf_pidgeon1972_awc`](headers/pidgeon1972.md#function-calc_ptf_pidgeon1972_awc) | Estimate available water capacity from clay and organic matter. | [``](headers/pidgeon1972.md) | | [`calc_ptf_pidgeon1972_awc_coarse_sand`](headers/pidgeon1972.md#function-calc_ptf_pidgeon1972_awc_coarse_sand) | Estimate available water capacity from coarse sand. | [``](headers/pidgeon1972.md) | | [`calc_ptf_pidgeon1972_awc_fine_sand`](headers/pidgeon1972.md#function-calc_ptf_pidgeon1972_awc_fine_sand) | Estimate available water capacity from fine sand. | [``](headers/pidgeon1972.md) | @@ -99,6 +104,13 @@ title: C function index | [`calc_ptf_rawls1982_full_wrc`](headers/rawls1982.md#function-calc_ptf_rawls1982_full_wrc) | Estimate a twelve-point water-retention curve using theta_33 and theta_1500. | [``](headers/rawls1982.md) | | [`calc_ptf_rawls1982_theta_33`](headers/rawls1982.md#function-calc_ptf_rawls1982_theta_33) | Estimate volumetric water content at -33 kPa using measured or estimated theta_1500. | [``](headers/rawls1982.md) | | [`calc_ptf_rawls1982_theta_1500`](headers/rawls1982.md#function-calc_ptf_rawls1982_theta_1500) | Estimate volumetric water content at -1500 kPa. | [``](headers/rawls1982.md) | +| [`calc_ptf_saxton2006`](headers/saxton2006.md#function-calc_ptf_saxton2006) | Estimate soil water characteristics from sand, clay, and organic matter. | [``](headers/saxton2006.md) | +| [`calc_ptf_saxton2006_conductivity`](headers/saxton2006.md#function-calc_ptf_saxton2006_conductivity) | Estimate unsaturated hydraulic conductivity from water content. | [``](headers/saxton2006.md) | +| [`calc_ptf_saxton2006_density`](headers/saxton2006.md#function-calc_ptf_saxton2006_density) | Adjust Saxton and Rawls water characteristics for soil density. | [``](headers/saxton2006.md) | +| [`calc_ptf_saxton2006_gravel`](headers/saxton2006.md#function-calc_ptf_saxton2006_gravel) | Adjust matric-soil properties for gravel content. | [``](headers/saxton2006.md) | +| [`calc_ptf_saxton2006_salinity`](headers/saxton2006.md#function-calc_ptf_saxton2006_salinity) | Estimate saturated and moisture-adjusted osmotic potential. | [``](headers/saxton2006.md) | +| [`calc_ptf_saxton2006_tension_dry`](headers/saxton2006.md#function-calc_ptf_saxton2006_tension_dry) | Estimate matric tension in the 1500 to 33 kPa segment. | [``](headers/saxton2006.md) | +| [`calc_ptf_saxton2006_tension_wet`](headers/saxton2006.md#function-calc_ptf_saxton2006_tension_wet) | Estimate matric tension in the 33 kPa to air-entry segment. | [``](headers/saxton2006.md) | | [`calc_ptf_tiwary2014_bsr`](headers/tiwary2014.md#function-calc_ptf_tiwary2014_bsr) | Estimate water retention and saturated conductivity for the black soil region. | [``](headers/tiwary2014.md) | | [`calc_ptf_tiwary2014_igp`](headers/tiwary2014.md#function-calc_ptf_tiwary2014_igp) | Estimate saturated conductivity for Indo-Gangetic Plains soils. | [``](headers/tiwary2014.md) | | [`calc_ptf_varallyai1982_chernozem_a`](headers/varallyai1982.md#function-calc_ptf_varallyai1982_chernozem_a) | Estimate equation (9) water-retention parameters for chernozem A horizons. | [``](headers/varallyai1982.md) | diff --git a/docs/src/reference/c/headers/oosterveld1980.md b/docs/src/reference/c/headers/oosterveld1980.md new file mode 100644 index 0000000..04a1037 --- /dev/null +++ b/docs/src/reference/c/headers/oosterveld1980.md @@ -0,0 +1,154 @@ +--- +title: "oosterveld1980.h" +--- + + + +# `` + +```c +#include +``` + +Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + +## Source + +Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory determinations of soil texture and moisture retention. Canadian Agricultural Engineering, 22, 149-151. + +## Scope + +**Territory:** Southern Alberta, Canada + +**Dataset:** 1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research Station laboratory; the field-capacity tension regression used 134 samples. + +[PTF catalog page](../../../ptf-catalog/sources/oosterveld1980.md) + +## Functions + +### `calc_ptf_oosterveld1980_field_capacity_tension` {#function-calc_ptf_oosterveld1980_field_capacity_tension} + +Estimate field-capacity tension from clay content. + +```c +static inline double calc_ptf_oosterveld1980_field_capacity_tension(double clay); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `clay` | in | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. (% mass) | + +#### Returns + +Predicted tension corresponding to cylinder-method field capacity. (kPa) + +!!! note + + Equation 1 reports r = 0.67 and n = 134. + +### `calc_ptf_oosterveld1980_retention` {#function-calc_ptf_oosterveld1980_retention} + +Estimate gravimetric soil-moisture content from texture, depth, and tension. + +```c +static inline double calc_ptf_oosterveld1980_retention(double clay, double sand, double mean_depth, double tension); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `clay` | in | Clay content in percent by weight. (% mass) | +| `sand` | in | Sand content in percent by weight. (% mass) | +| `mean_depth` | in | Mean depth of the soil sample. (cm) | +| `tension` | in | Soil-moisture tension. (kPa) | + +#### Returns + +Predicted soil-moisture content in percent by weight. (% mass) + +!!! note + + Equation 2 reports r = 0.96 and n = 1,137. + +!!! warning + + The paper reports slight inaccuracy at very high clay content and high tension. + +### `calc_ptf_oosterveld1980_field_capacity` {#function-calc_ptf_oosterveld1980_field_capacity} + +Estimate gravimetric soil-moisture content at field capacity. + +```c +static inline double calc_ptf_oosterveld1980_field_capacity(double clay, double sand, double mean_depth); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `clay` | in | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. (% mass) | +| `sand` | in | Sand content in percent by weight. (% mass) | +| `mean_depth` | in | Mean depth of the soil sample. (cm) | + +#### Returns + +Predicted soil-moisture content at field capacity in percent by weight. (% mass) + +!!! note + + Equation 3 is obtained by substituting the Equation 1 field-capacity tension into Equation 2. + +### `calc_ptf_oosterveld1980_wilting_point` {#function-calc_ptf_oosterveld1980_wilting_point} + +Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + +```c +static inline double calc_ptf_oosterveld1980_wilting_point(double clay, double sand, double mean_depth); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `clay` | in | Clay content in percent by weight. (% mass) | +| `sand` | in | Sand content in percent by weight. (% mass) | +| `mean_depth` | in | Mean depth of the soil sample. (cm) | + +#### Returns + +Predicted soil-moisture content at 1500 kPa in percent by weight. (% mass) + +!!! note + + Equation 4 reports r = 0.96 and n = 298. + +!!! note + + The paper takes moisture content at 1500 kPa as the wilting point. + +### `calc_ptf_oosterveld1980_available_water` {#function-calc_ptf_oosterveld1980_available_water} + +Estimate available gravimetric soil moisture between field capacity and wilting point. + +```c +static inline double calc_ptf_oosterveld1980_available_water(double clay, double sand, double mean_depth); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `clay` | in | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. (% mass) | +| `sand` | in | Sand content in percent by weight. (% mass) | +| `mean_depth` | in | Mean depth of the soil sample. (cm) | + +#### Returns + +Predicted field-capacity moisture minus predicted wilting-point moisture, in percentage points by weight. (% mass) + +!!! note + + The paper defines available soil water by subtracting Equation 4 from Equation 3. diff --git a/docs/src/reference/c/headers/ptfkit.md b/docs/src/reference/c/headers/ptfkit.md index b0814dc..099a16c 100644 --- a/docs/src/reference/c/headers/ptfkit.md +++ b/docs/src/reference/c/headers/ptfkit.md @@ -25,9 +25,11 @@ This umbrella header aggregates every public ptfkit source header. Include an in - [``](jabro1992.md) — Jabro (1992), United States. - [``](li2007.md) — Li et al. (2007), Fengqiu County, North China Plain, China. - [``](mayr1999.md) — Mayr and Jarvis (1999) modified Brooks-Corey water-retention parameter PTFs. +- [``](oosterveld1980.md) — Oosterveld and Chang (1980), soil-moisture retention from texture and depth. - [``](pidgeon1972.md) — Pidgeon (1972), available-water regressions for ferrallitic soils in Uganda. - [``](puckett1985.md) — Puckett et al. (1985), Alabama Lower Coastal Plain Ultisols. - [``](rawls1982.md) — Rawls et al. (1982), water-retention regressions for soils across the USA. +- [``](saxton2006.md) — Saxton and Rawls (2006), soil water characteristics from texture and organic matter. - [``](tiwary2014.md) — Tiwary et al. (2014), hydraulic PTFs for two major soil regions of India. - [``](varallyai1982.md) — Varallyai et al. (1982), water-retention parameter regressions for Hungarian soils. - [``](vereecken1989.md) — Vereecken et al. soil-moisture retention regressions for Belgian soils. diff --git a/docs/src/reference/c/headers/saxton2006.md b/docs/src/reference/c/headers/saxton2006.md new file mode 100644 index 0000000..48bc335 --- /dev/null +++ b/docs/src/reference/c/headers/saxton2006.md @@ -0,0 +1,317 @@ +--- +title: "saxton2006.h" +--- + + + +# `` + +```c +#include +``` + +Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + +## Source + +Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic estimates by texture and organic matter for hydrologic solutions. Soil Science Society of America Journal, 70(5), 1569-1578. + +[DOI: 10.2136/ssaj2005.0117](https://doi.org/10.2136/ssaj2005.0117) + +## Scope + +**Territory:** United States + +**Dataset:** 1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National Soil Characterization database after exclusions from 2,149 samples. + +[PTF catalog page](../../../ptf-catalog/sources/saxton2006.md) + +## `saxton2006_ptf_result` + +```c +typedef struct { + double theta_1500; + double theta_33; + double theta_s; + double plant_available_water; + double air_entry_tension; + double retention_a; + double retention_b; + double conductivity_lambda; + double saturated_conductivity; + double normal_density; + } saxton2006_ptf_result; +``` + +| Field | Description | +| --- | --- | +| `theta_1500` | Volumetric water content at 1500 kPa matric tension. (m^3/m^3) | +| `theta_33` | Volumetric water content at 33 kPa matric tension. (m^3/m^3) | +| `theta_s` | Saturated volumetric water content at normal density. (m^3/m^3) | +| `plant_available_water` | Difference between the 33 and 1500 kPa water contents. (m^3/m^3) | +| `air_entry_tension` | Air-entry or bubbling-pressure tension. (kPa) | +| `retention_a` | Coefficient A of the dry-range moisture-tension curve. (kPa) | +| `retention_b` | Exponent B of the dry-range moisture-tension curve. (dimensionless) | +| `conductivity_lambda` | Inverse of retention exponent B. (dimensionless) | +| `saturated_conductivity` | Saturated hydraulic conductivity of the matric soil. (mm/h) | +| `normal_density` | Normal dry bulk density assuming particle density 2.65 g/cm^3. (g/cm^3) | + +## `saxton2006_density_result` + +```c +typedef struct { + double adjusted_density; + double adjusted_theta_s; + double adjusted_theta_33; + double adjusted_theta_s_minus_33; + } saxton2006_density_result; +``` + +| Field | Description | +| --- | --- | +| `adjusted_density` | Density after applying the density factor. (g/cm^3) | +| `adjusted_theta_s` | Saturated water content after the density adjustment. (m^3/m^3) | +| `adjusted_theta_33` | Water content at 33 kPa after the density adjustment. (m^3/m^3) | +| `adjusted_theta_s_minus_33` | Density-adjusted difference between saturation and 33 kPa. (m^3/m^3) | + +## `saxton2006_gravel_result` + +```c +typedef struct { + double gravel_volume_fraction; + double bulk_density; + double bulk_plant_available_water; + double bulk_saturated_conductivity; + } saxton2006_gravel_result; +``` + +| Field | Description | +| --- | --- | +| `gravel_volume_fraction` | Gravel volume fraction of the bulk soil. (m^3/m^3) | +| `bulk_density` | Dry bulk density of matric soil plus gravel. (g/cm^3) | +| `bulk_plant_available_water` | Plant-available water on a bulk-soil volume basis. (m^3/m^3) | +| `bulk_saturated_conductivity` | Saturated conductivity after the gravel reduction. (mm/h) | + +## `saxton2006_salinity_result` + +```c +typedef struct { + double saturated_osmotic_potential; + double osmotic_potential; + } saxton2006_salinity_result; +``` + +| Field | Description | +| --- | --- | +| `saturated_osmotic_potential` | Osmotic potential at saturation. (kPa) | +| `osmotic_potential` | Osmotic potential at the supplied water content. (kPa) | + +## Functions + +### `calc_ptf_saxton2006` {#function-calc_ptf_saxton2006} + +Estimate soil water characteristics from sand, clay, and organic matter. + +```c +static inline saxton2006_ptf_result calc_ptf_saxton2006(double sand, double clay, double organic_matter); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `sand` | in | Sand mass fraction of the fine-earth soil. (g/g) | +| `clay` | in | Clay mass fraction of the fine-earth soil. (g/g) | +| `organic_matter` | in | Organic matter content on a mass percentage basis. (% mass) | + +#### Returns + +A `saxton2006_ptf_result` value. + +!!! note + + Sand and clay are decimal mass fractions, while organic matter is a percentage. + +!!! note + + The 1500 and 33 kPa values are also termed wilting point and field capacity. + +!!! warning + + Do not apply the regression above 8% organic matter or 60% clay. + +!!! warning + + Sand and clay fractions must describe one soil and therefore must sum to at most 1. + +!!! warning + + These statistical-average estimates should be calibrated to local measurements when available. + +### `calc_ptf_saxton2006_density` {#function-calc_ptf_saxton2006_density} + +Adjust Saxton and Rawls water characteristics for soil density. + +```c +static inline saxton2006_density_result calc_ptf_saxton2006_density(double normal_density, double theta_s, double theta_33, double density_factor); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `normal_density` | in | Normal dry bulk density estimated by the base model. (g/cm^3) | +| `theta_s` | in | Saturated water content at normal density. (m^3/m^3) | +| `theta_33` | in | Water content at 33 kPa and normal density. (m^3/m^3) | +| `density_factor` | in | Multiplicative adjustment to normal density. (dimensionless) | + +#### Returns + +A `saxton2006_density_result` value. + +!!! note + + The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + +!!! warning + + The source recommends density factors only from 0.9 to 1.3. + +### `calc_ptf_saxton2006_tension_dry` {#function-calc_ptf_saxton2006_tension_dry} + +Estimate matric tension in the 1500 to 33 kPa segment. + +```c +static inline double calc_ptf_saxton2006_tension_dry(double theta, double theta_1500, double theta_33); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `theta` | in | Volumetric water content. (m^3/m^3) | +| `theta_1500` | in | Volumetric water content at 1500 kPa. (m^3/m^3) | +| `theta_33` | in | Volumetric water content at 33 kPa. (m^3/m^3) | + +#### Returns + +Matric tension at the supplied water content. (kPa) + +!!! warning + + Use only for the 1500 to 33 kPa segment defined by the source. + +### `calc_ptf_saxton2006_tension_wet` {#function-calc_ptf_saxton2006_tension_wet} + +Estimate matric tension in the 33 kPa to air-entry segment. + +```c +static inline double calc_ptf_saxton2006_tension_wet(double theta, double theta_33, double theta_s, double air_entry_tension); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `theta` | in | Volumetric water content. (m^3/m^3) | +| `theta_33` | in | Volumetric water content at 33 kPa. (m^3/m^3) | +| `theta_s` | in | Saturated volumetric water content. (m^3/m^3) | +| `air_entry_tension` | in | Air-entry tension estimated by the base model. (kPa) | + +#### Returns + +Matric tension at the supplied water content. (kPa) + +!!! note + + At tensions below air entry, Equation 13 fixes water content at theta_s. + +!!! warning + + Use only for the 33 kPa to air-entry segment defined by the source. + +### `calc_ptf_saxton2006_conductivity` {#function-calc_ptf_saxton2006_conductivity} + +Estimate unsaturated hydraulic conductivity from water content. + +```c +static inline double calc_ptf_saxton2006_conductivity(double theta, double theta_s, double saturated_conductivity, double conductivity_lambda); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `theta` | in | Volumetric water content. (m^3/m^3) | +| `theta_s` | in | Saturated volumetric water content. (m^3/m^3) | +| `saturated_conductivity` | in | Saturated hydraulic conductivity of the matric soil. (mm/h) | +| `conductivity_lambda` | in | Inverse of retention exponent B. (dimensionless) | + +#### Returns + +Unsaturated hydraulic conductivity at the supplied water content. (mm/h) + +!!! warning + + The equation does not include residual water content. + +### `calc_ptf_saxton2006_gravel` {#function-calc_ptf_saxton2006_gravel} + +Adjust matric-soil properties for gravel content. + +```c +static inline saxton2006_gravel_result calc_ptf_saxton2006_gravel(double gravel_weight_fraction, double matric_density, double plant_available_water, double saturated_conductivity); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `gravel_weight_fraction` | in | Gravel mass fraction of the bulk soil. (g/g) | +| `matric_density` | in | Dry bulk density of the fine-earth matric soil. (g/cm^3) | +| `plant_available_water` | in | Plant-available water of the matric soil. (m^3/m^3) | +| `saturated_conductivity` | in | Saturated hydraulic conductivity of the matric soil. (mm/h) | + +#### Returns + +A `saxton2006_gravel_result` value. + +!!! note + + Gravel comprises particles larger than 2 mm. + +!!! note + + Gravel density is fixed at 2.65 g/cm^3 in the source equations. + +!!! warning + + The conductivity correction does not represent extra macropores sometimes found in gravelly soils. + +### `calc_ptf_saxton2006_salinity` {#function-calc_ptf_saxton2006_salinity} + +Estimate saturated and moisture-adjusted osmotic potential. + +```c +static inline saxton2006_salinity_result calc_ptf_saxton2006_salinity(double electrical_conductivity, double theta, double theta_s); +``` + +#### Parameters + +| Name | Direction | Description | +| --- | --- | --- | +| `electrical_conductivity` | in | Electrical conductivity of a saturated soil extract. (dS/m) | +| `theta` | in | Current volumetric water content. (m^3/m^3) | +| `theta_s` | in | Saturated volumetric water content. (m^3/m^3) | + +#### Returns + +A `saxton2006_salinity_result` value. + +!!! note + + The moisture adjustment assumes dissolved salt quantity remains near constant as water content falls. + +!!! warning + + Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity effects. diff --git a/docs/src/reference/c/index.md b/docs/src/reference/c/index.md index d84e08b..477636e 100644 --- a/docs/src/reference/c/index.md +++ b/docs/src/reference/c/index.md @@ -22,9 +22,11 @@ ptfkit's C API is organized around installed headers. - [``](headers/jabro1992.md) — Jabro (1992), United States. - [``](headers/li2007.md) — Li et al. (2007), Fengqiu County, North China Plain, China. - [``](headers/mayr1999.md) — Mayr and Jarvis (1999) modified Brooks-Corey water-retention parameter PTFs. +- [``](headers/oosterveld1980.md) — Oosterveld and Chang (1980), soil-moisture retention from texture and depth. - [``](headers/pidgeon1972.md) — Pidgeon (1972), available-water regressions for ferrallitic soils in Uganda. - [``](headers/puckett1985.md) — Puckett et al. (1985), Alabama Lower Coastal Plain Ultisols. - [``](headers/rawls1982.md) — Rawls et al. (1982), water-retention regressions for soils across the USA. +- [``](headers/saxton2006.md) — Saxton and Rawls (2006), soil water characteristics from texture and organic matter. - [``](headers/tiwary2014.md) — Tiwary et al. (2014), hydraulic PTFs for two major soil regions of India. - [``](headers/varallyai1982.md) — Varallyai et al. (1982), water-retention parameter regressions for Hungarian soils. - [``](headers/vereecken1989.md) — Vereecken et al. soil-moisture retention regressions for Belgian soils. diff --git a/docs/src/reference/cpp/functions.md b/docs/src/reference/cpp/functions.md index d772c9c..8e07987 100644 --- a/docs/src/reference/cpp/functions.md +++ b/docs/src/reference/cpp/functions.md @@ -78,6 +78,11 @@ title: C++ function index | [`ptfkit::jabro1992::calc_ptf_jabro1992`](modules/jabro1992.md#function-calc_ptf_jabro1992) | Estimate saturated hydraulic conductivity from silt, clay, and bulk density. | [`ptfkit.jabro1992`](modules/jabro1992.md) | | [`ptfkit::li2007::calc_ptf_li2007`](modules/li2007.md#function-calc_ptf_li2007) | Estimate van Genuchten parameters and saturated hydraulic conductivity for Fengqiu County soils. | [`ptfkit.li2007`](modules/li2007.md) | | [`ptfkit::mayr1999::calc_ptf_mayr1999`](modules/mayr1999.md#function-calc_ptf_mayr1999) | Estimate modified Brooks-Corey a, b, and saturated water content from texture, bulk density, and organic carbon. | [`ptfkit.mayr1999`](modules/mayr1999.md) | +| [`ptfkit::oosterveld1980::calc_ptf_oosterveld1980_available_water`](modules/oosterveld1980.md#function-calc_ptf_oosterveld1980_available_water) | Estimate available gravimetric soil moisture between field capacity and wilting point. | [`ptfkit.oosterveld1980`](modules/oosterveld1980.md) | +| [`ptfkit::oosterveld1980::calc_ptf_oosterveld1980_field_capacity`](modules/oosterveld1980.md#function-calc_ptf_oosterveld1980_field_capacity) | Estimate gravimetric soil-moisture content at field capacity. | [`ptfkit.oosterveld1980`](modules/oosterveld1980.md) | +| [`ptfkit::oosterveld1980::calc_ptf_oosterveld1980_field_capacity_tension`](modules/oosterveld1980.md#function-calc_ptf_oosterveld1980_field_capacity_tension) | Estimate field-capacity tension from clay content. | [`ptfkit.oosterveld1980`](modules/oosterveld1980.md) | +| [`ptfkit::oosterveld1980::calc_ptf_oosterveld1980_retention`](modules/oosterveld1980.md#function-calc_ptf_oosterveld1980_retention) | Estimate gravimetric soil-moisture content from texture, depth, and tension. | [`ptfkit.oosterveld1980`](modules/oosterveld1980.md) | +| [`ptfkit::oosterveld1980::calc_ptf_oosterveld1980_wilting_point`](modules/oosterveld1980.md#function-calc_ptf_oosterveld1980_wilting_point) | Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. | [`ptfkit.oosterveld1980`](modules/oosterveld1980.md) | | [`ptfkit::pidgeon1972::calc_ptf_pidgeon1972_awc`](modules/pidgeon1972.md#function-calc_ptf_pidgeon1972_awc) | Estimate available water capacity from clay and organic matter. | [`ptfkit.pidgeon1972`](modules/pidgeon1972.md) | | [`ptfkit::pidgeon1972::calc_ptf_pidgeon1972_awc_coarse_sand`](modules/pidgeon1972.md#function-calc_ptf_pidgeon1972_awc_coarse_sand) | Estimate available water capacity from coarse sand. | [`ptfkit.pidgeon1972`](modules/pidgeon1972.md) | | [`ptfkit::pidgeon1972::calc_ptf_pidgeon1972_awc_fine_sand`](modules/pidgeon1972.md#function-calc_ptf_pidgeon1972_awc_fine_sand) | Estimate available water capacity from fine sand. | [`ptfkit.pidgeon1972`](modules/pidgeon1972.md) | @@ -99,6 +104,13 @@ title: C++ function index | [`ptfkit::rawls1982::calc_ptf_rawls1982_full_wrc`](modules/rawls1982.md#function-calc_ptf_rawls1982_full_wrc) | Estimate a twelve-point water-retention curve using theta_33 and theta_1500. | [`ptfkit.rawls1982`](modules/rawls1982.md) | | [`ptfkit::rawls1982::calc_ptf_rawls1982_theta_33`](modules/rawls1982.md#function-calc_ptf_rawls1982_theta_33) | Estimate volumetric water content at -33 kPa using measured or estimated theta_1500. | [`ptfkit.rawls1982`](modules/rawls1982.md) | | [`ptfkit::rawls1982::calc_ptf_rawls1982_theta_1500`](modules/rawls1982.md#function-calc_ptf_rawls1982_theta_1500) | Estimate volumetric water content at -1500 kPa. | [`ptfkit.rawls1982`](modules/rawls1982.md) | +| [`ptfkit::saxton2006::calc_ptf_saxton2006`](modules/saxton2006.md#function-calc_ptf_saxton2006) | Estimate soil water characteristics from sand, clay, and organic matter. | [`ptfkit.saxton2006`](modules/saxton2006.md) | +| [`ptfkit::saxton2006::calc_ptf_saxton2006_conductivity`](modules/saxton2006.md#function-calc_ptf_saxton2006_conductivity) | Estimate unsaturated hydraulic conductivity from water content. | [`ptfkit.saxton2006`](modules/saxton2006.md) | +| [`ptfkit::saxton2006::calc_ptf_saxton2006_density`](modules/saxton2006.md#function-calc_ptf_saxton2006_density) | Adjust Saxton and Rawls water characteristics for soil density. | [`ptfkit.saxton2006`](modules/saxton2006.md) | +| [`ptfkit::saxton2006::calc_ptf_saxton2006_gravel`](modules/saxton2006.md#function-calc_ptf_saxton2006_gravel) | Adjust matric-soil properties for gravel content. | [`ptfkit.saxton2006`](modules/saxton2006.md) | +| [`ptfkit::saxton2006::calc_ptf_saxton2006_salinity`](modules/saxton2006.md#function-calc_ptf_saxton2006_salinity) | Estimate saturated and moisture-adjusted osmotic potential. | [`ptfkit.saxton2006`](modules/saxton2006.md) | +| [`ptfkit::saxton2006::calc_ptf_saxton2006_tension_dry`](modules/saxton2006.md#function-calc_ptf_saxton2006_tension_dry) | Estimate matric tension in the 1500 to 33 kPa segment. | [`ptfkit.saxton2006`](modules/saxton2006.md) | +| [`ptfkit::saxton2006::calc_ptf_saxton2006_tension_wet`](modules/saxton2006.md#function-calc_ptf_saxton2006_tension_wet) | Estimate matric tension in the 33 kPa to air-entry segment. | [`ptfkit.saxton2006`](modules/saxton2006.md) | | [`ptfkit::tiwary2014::calc_ptf_tiwary2014_bsr`](modules/tiwary2014.md#function-calc_ptf_tiwary2014_bsr) | Estimate water retention and saturated conductivity for the black soil region. | [`ptfkit.tiwary2014`](modules/tiwary2014.md) | | [`ptfkit::tiwary2014::calc_ptf_tiwary2014_igp`](modules/tiwary2014.md#function-calc_ptf_tiwary2014_igp) | Estimate saturated conductivity for Indo-Gangetic Plains soils. | [`ptfkit.tiwary2014`](modules/tiwary2014.md) | | [`ptfkit::varallyai1982::calc_ptf_varallyai1982_chernozem_a`](modules/varallyai1982.md#function-calc_ptf_varallyai1982_chernozem_a) | Estimate equation (9) water-retention parameters for chernozem A horizons. | [`ptfkit.varallyai1982`](modules/varallyai1982.md) | diff --git a/docs/src/reference/cpp/index.md b/docs/src/reference/cpp/index.md index 3c5f305..f32a2e3 100644 --- a/docs/src/reference/cpp/index.md +++ b/docs/src/reference/cpp/index.md @@ -22,9 +22,11 @@ ptfkit's C++ API is organized around C++20 modules. - [`ptfkit.jabro1992`](modules/jabro1992.md) — Jabro (1992), United States. - [`ptfkit.li2007`](modules/li2007.md) — Li et al. (2007), Fengqiu County, North China Plain, China. - [`ptfkit.mayr1999`](modules/mayr1999.md) — Mayr and Jarvis (1999) modified Brooks-Corey water-retention parameter PTFs. +- [`ptfkit.oosterveld1980`](modules/oosterveld1980.md) — Oosterveld and Chang (1980), soil-moisture retention from texture and depth. - [`ptfkit.pidgeon1972`](modules/pidgeon1972.md) — Pidgeon (1972), available-water regressions for ferrallitic soils in Uganda. - [`ptfkit.puckett1985`](modules/puckett1985.md) — Puckett et al. (1985), Alabama Lower Coastal Plain Ultisols. - [`ptfkit.rawls1982`](modules/rawls1982.md) — Rawls et al. (1982), water-retention regressions for soils across the USA. +- [`ptfkit.saxton2006`](modules/saxton2006.md) — Saxton and Rawls (2006), soil water characteristics from texture and organic matter. - [`ptfkit.tiwary2014`](modules/tiwary2014.md) — Tiwary et al. (2014), hydraulic PTFs for two major soil regions of India. - [`ptfkit.varallyai1982`](modules/varallyai1982.md) — Varallyai et al. (1982), water-retention parameter regressions for Hungarian soils. - [`ptfkit.vereecken1989`](modules/vereecken1989.md) — Vereecken et al. soil-moisture retention regressions for Belgian soils. diff --git a/docs/src/reference/cpp/modules/oosterveld1980.md b/docs/src/reference/cpp/modules/oosterveld1980.md new file mode 100644 index 0000000..bb1ab05 --- /dev/null +++ b/docs/src/reference/cpp/modules/oosterveld1980.md @@ -0,0 +1,162 @@ +--- +# @generated by ptfkit-codegen; DO NOT EDIT. + +title: C++ module ptfkit.oosterveld1980 +nav-title: ptfkit.oosterveld1980 +--- + +# `ptfkit.oosterveld1980` + +```cpp +import ptfkit.oosterveld1980; +``` + +**Exported namespace:** `ptfkit::oosterveld1980` + +Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + +## Source + +Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory determinations of soil texture and moisture retention. Canadian Agricultural Engineering, 22, 149-151. + +## Scope + +**Territory:** Southern Alberta, Canada + +**Dataset:** 1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research Station laboratory; the field-capacity tension regression used 134 samples. + +[PTF catalog page](../../../ptf-catalog/sources/oosterveld1980.md) + +## Functions + +### `calc_ptf_oosterveld1980_field_capacity_tension` {#function-calc_ptf_oosterveld1980_field_capacity_tension} + +Estimate field-capacity tension from clay content. + +```cpp +[[nodiscard]] +inline double calc_ptf_oosterveld1980_field_capacity_tension(double clay) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `clay` | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. (% mass) | + +#### Returns + +Predicted tension corresponding to cylinder-method field capacity. (kPa) + +!!! note + + Equation 1 reports r = 0.67 and n = 134. + +### `calc_ptf_oosterveld1980_retention` {#function-calc_ptf_oosterveld1980_retention} + +Estimate gravimetric soil-moisture content from texture, depth, and tension. + +```cpp +[[nodiscard]] +inline double calc_ptf_oosterveld1980_retention(double clay, double sand, double mean_depth, double tension) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `clay` | Clay content in percent by weight. (% mass) | +| `sand` | Sand content in percent by weight. (% mass) | +| `mean_depth` | Mean depth of the soil sample. (cm) | +| `tension` | Soil-moisture tension. (kPa) | + +#### Returns + +Predicted soil-moisture content in percent by weight. (% mass) + +!!! note + + Equation 2 reports r = 0.96 and n = 1,137. + +!!! warning + + The paper reports slight inaccuracy at very high clay content and high tension. + +### `calc_ptf_oosterveld1980_field_capacity` {#function-calc_ptf_oosterveld1980_field_capacity} + +Estimate gravimetric soil-moisture content at field capacity. + +```cpp +[[nodiscard]] +inline double calc_ptf_oosterveld1980_field_capacity(double clay, double sand, double mean_depth) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `clay` | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. (% mass) | +| `sand` | Sand content in percent by weight. (% mass) | +| `mean_depth` | Mean depth of the soil sample. (cm) | + +#### Returns + +Predicted soil-moisture content at field capacity in percent by weight. (% mass) + +!!! note + + Equation 3 is obtained by substituting the Equation 1 field-capacity tension into Equation 2. + +### `calc_ptf_oosterveld1980_wilting_point` {#function-calc_ptf_oosterveld1980_wilting_point} + +Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + +```cpp +[[nodiscard]] +inline double calc_ptf_oosterveld1980_wilting_point(double clay, double sand, double mean_depth) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `clay` | Clay content in percent by weight. (% mass) | +| `sand` | Sand content in percent by weight. (% mass) | +| `mean_depth` | Mean depth of the soil sample. (cm) | + +#### Returns + +Predicted soil-moisture content at 1500 kPa in percent by weight. (% mass) + +!!! note + + Equation 4 reports r = 0.96 and n = 298. + +!!! note + + The paper takes moisture content at 1500 kPa as the wilting point. + +### `calc_ptf_oosterveld1980_available_water` {#function-calc_ptf_oosterveld1980_available_water} + +Estimate available gravimetric soil moisture between field capacity and wilting point. + +```cpp +[[nodiscard]] +inline double calc_ptf_oosterveld1980_available_water(double clay, double sand, double mean_depth) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `clay` | Clay content in percent by weight; the field-capacity tension experiment covered 2% to 72% clay. (% mass) | +| `sand` | Sand content in percent by weight. (% mass) | +| `mean_depth` | Mean depth of the soil sample. (cm) | + +#### Returns + +Predicted field-capacity moisture minus predicted wilting-point moisture, in percentage points by weight. (% mass) + +!!! note + + The paper defines available soil water by subtracting Equation 4 from Equation 3. diff --git a/docs/src/reference/cpp/modules/ptfkit.md b/docs/src/reference/cpp/modules/ptfkit.md index 9b76290..2025080 100644 --- a/docs/src/reference/cpp/modules/ptfkit.md +++ b/docs/src/reference/cpp/modules/ptfkit.md @@ -26,9 +26,11 @@ This umbrella module re-exports every public ptfkit source module. Import an ind - [`ptfkit.jabro1992`](jabro1992.md) — Jabro (1992), United States. - [`ptfkit.li2007`](li2007.md) — Li et al. (2007), Fengqiu County, North China Plain, China. - [`ptfkit.mayr1999`](mayr1999.md) — Mayr and Jarvis (1999) modified Brooks-Corey water-retention parameter PTFs. +- [`ptfkit.oosterveld1980`](oosterveld1980.md) — Oosterveld and Chang (1980), soil-moisture retention from texture and depth. - [`ptfkit.pidgeon1972`](pidgeon1972.md) — Pidgeon (1972), available-water regressions for ferrallitic soils in Uganda. - [`ptfkit.puckett1985`](puckett1985.md) — Puckett et al. (1985), Alabama Lower Coastal Plain Ultisols. - [`ptfkit.rawls1982`](rawls1982.md) — Rawls et al. (1982), water-retention regressions for soils across the USA. +- [`ptfkit.saxton2006`](saxton2006.md) — Saxton and Rawls (2006), soil water characteristics from texture and organic matter. - [`ptfkit.tiwary2014`](tiwary2014.md) — Tiwary et al. (2014), hydraulic PTFs for two major soil regions of India. - [`ptfkit.varallyai1982`](varallyai1982.md) — Varallyai et al. (1982), water-retention parameter regressions for Hungarian soils. - [`ptfkit.vereecken1989`](vereecken1989.md) — Vereecken et al. soil-moisture retention regressions for Belgian soils. diff --git a/docs/src/reference/cpp/modules/saxton2006.md b/docs/src/reference/cpp/modules/saxton2006.md new file mode 100644 index 0000000..0e79dc4 --- /dev/null +++ b/docs/src/reference/cpp/modules/saxton2006.md @@ -0,0 +1,327 @@ +--- +# @generated by ptfkit-codegen; DO NOT EDIT. + +title: C++ module ptfkit.saxton2006 +nav-title: ptfkit.saxton2006 +--- + +# `ptfkit.saxton2006` + +```cpp +import ptfkit.saxton2006; +``` + +**Exported namespace:** `ptfkit::saxton2006` + +Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + +## Source + +Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic estimates by texture and organic matter for hydrologic solutions. Soil Science Society of America Journal, 70(5), 1569-1578. + +[DOI: 10.2136/ssaj2005.0117](https://doi.org/10.2136/ssaj2005.0117) + +## Scope + +**Territory:** United States + +**Dataset:** 1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National Soil Characterization database after exclusions from 2,149 samples. + +[PTF catalog page](../../../ptf-catalog/sources/saxton2006.md) + +## `Saxton2006PTFResult` + +```cpp +struct Saxton2006PTFResult { + double theta_1500; + double theta_33; + double theta_s; + double plant_available_water; + double air_entry_tension; + double retention_a; + double retention_b; + double conductivity_lambda; + double saturated_conductivity; + double normal_density; +}; +``` + +| Field | Description | +| --- | --- | +| `theta_1500` | Volumetric water content at 1500 kPa matric tension. (m^3/m^3) | +| `theta_33` | Volumetric water content at 33 kPa matric tension. (m^3/m^3) | +| `theta_s` | Saturated volumetric water content at normal density. (m^3/m^3) | +| `plant_available_water` | Difference between the 33 and 1500 kPa water contents. (m^3/m^3) | +| `air_entry_tension` | Air-entry or bubbling-pressure tension. (kPa) | +| `retention_a` | Coefficient A of the dry-range moisture-tension curve. (kPa) | +| `retention_b` | Exponent B of the dry-range moisture-tension curve. (dimensionless) | +| `conductivity_lambda` | Inverse of retention exponent B. (dimensionless) | +| `saturated_conductivity` | Saturated hydraulic conductivity of the matric soil. (mm/h) | +| `normal_density` | Normal dry bulk density assuming particle density 2.65 g/cm^3. (g/cm^3) | + +## `Saxton2006DensityResult` + +```cpp +struct Saxton2006DensityResult { + double adjusted_density; + double adjusted_theta_s; + double adjusted_theta_33; + double adjusted_theta_s_minus_33; +}; +``` + +| Field | Description | +| --- | --- | +| `adjusted_density` | Density after applying the density factor. (g/cm^3) | +| `adjusted_theta_s` | Saturated water content after the density adjustment. (m^3/m^3) | +| `adjusted_theta_33` | Water content at 33 kPa after the density adjustment. (m^3/m^3) | +| `adjusted_theta_s_minus_33` | Density-adjusted difference between saturation and 33 kPa. (m^3/m^3) | + +## `Saxton2006GravelResult` + +```cpp +struct Saxton2006GravelResult { + double gravel_volume_fraction; + double bulk_density; + double bulk_plant_available_water; + double bulk_saturated_conductivity; +}; +``` + +| Field | Description | +| --- | --- | +| `gravel_volume_fraction` | Gravel volume fraction of the bulk soil. (m^3/m^3) | +| `bulk_density` | Dry bulk density of matric soil plus gravel. (g/cm^3) | +| `bulk_plant_available_water` | Plant-available water on a bulk-soil volume basis. (m^3/m^3) | +| `bulk_saturated_conductivity` | Saturated conductivity after the gravel reduction. (mm/h) | + +## `Saxton2006SalinityResult` + +```cpp +struct Saxton2006SalinityResult { + double saturated_osmotic_potential; + double osmotic_potential; +}; +``` + +| Field | Description | +| --- | --- | +| `saturated_osmotic_potential` | Osmotic potential at saturation. (kPa) | +| `osmotic_potential` | Osmotic potential at the supplied water content. (kPa) | + +## Functions + +### `calc_ptf_saxton2006` {#function-calc_ptf_saxton2006} + +Estimate soil water characteristics from sand, clay, and organic matter. + +```cpp +[[nodiscard]] +inline Saxton2006PTFResult calc_ptf_saxton2006(double sand, double clay, double organic_matter) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `sand` | Sand mass fraction of the fine-earth soil. (g/g) | +| `clay` | Clay mass fraction of the fine-earth soil. (g/g) | +| `organic_matter` | Organic matter content on a mass percentage basis. (% mass) | + +#### Returns + +A `Saxton2006PTFResult` value. + +!!! note + + Sand and clay are decimal mass fractions, while organic matter is a percentage. + +!!! note + + The 1500 and 33 kPa values are also termed wilting point and field capacity. + +!!! warning + + Do not apply the regression above 8% organic matter or 60% clay. + +!!! warning + + Sand and clay fractions must describe one soil and therefore must sum to at most 1. + +!!! warning + + These statistical-average estimates should be calibrated to local measurements when available. + +### `calc_ptf_saxton2006_density` {#function-calc_ptf_saxton2006_density} + +Adjust Saxton and Rawls water characteristics for soil density. + +```cpp +[[nodiscard]] +inline Saxton2006DensityResult calc_ptf_saxton2006_density(double normal_density, double theta_s, double theta_33, double density_factor) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `normal_density` | Normal dry bulk density estimated by the base model. (g/cm^3) | +| `theta_s` | Saturated water content at normal density. (m^3/m^3) | +| `theta_33` | Water content at 33 kPa and normal density. (m^3/m^3) | +| `density_factor` | Multiplicative adjustment to normal density. (dimensionless) | + +#### Returns + +A `Saxton2006DensityResult` value. + +!!! note + + The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + +!!! warning + + The source recommends density factors only from 0.9 to 1.3. + +### `calc_ptf_saxton2006_tension_dry` {#function-calc_ptf_saxton2006_tension_dry} + +Estimate matric tension in the 1500 to 33 kPa segment. + +```cpp +[[nodiscard]] +inline double calc_ptf_saxton2006_tension_dry(double theta, double theta_1500, double theta_33) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `theta` | Volumetric water content. (m^3/m^3) | +| `theta_1500` | Volumetric water content at 1500 kPa. (m^3/m^3) | +| `theta_33` | Volumetric water content at 33 kPa. (m^3/m^3) | + +#### Returns + +Matric tension at the supplied water content. (kPa) + +!!! warning + + Use only for the 1500 to 33 kPa segment defined by the source. + +### `calc_ptf_saxton2006_tension_wet` {#function-calc_ptf_saxton2006_tension_wet} + +Estimate matric tension in the 33 kPa to air-entry segment. + +```cpp +[[nodiscard]] +inline double calc_ptf_saxton2006_tension_wet(double theta, double theta_33, double theta_s, double air_entry_tension) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `theta` | Volumetric water content. (m^3/m^3) | +| `theta_33` | Volumetric water content at 33 kPa. (m^3/m^3) | +| `theta_s` | Saturated volumetric water content. (m^3/m^3) | +| `air_entry_tension` | Air-entry tension estimated by the base model. (kPa) | + +#### Returns + +Matric tension at the supplied water content. (kPa) + +!!! note + + At tensions below air entry, Equation 13 fixes water content at theta_s. + +!!! warning + + Use only for the 33 kPa to air-entry segment defined by the source. + +### `calc_ptf_saxton2006_conductivity` {#function-calc_ptf_saxton2006_conductivity} + +Estimate unsaturated hydraulic conductivity from water content. + +```cpp +[[nodiscard]] +inline double calc_ptf_saxton2006_conductivity(double theta, double theta_s, double saturated_conductivity, double conductivity_lambda) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `theta` | Volumetric water content. (m^3/m^3) | +| `theta_s` | Saturated volumetric water content. (m^3/m^3) | +| `saturated_conductivity` | Saturated hydraulic conductivity of the matric soil. (mm/h) | +| `conductivity_lambda` | Inverse of retention exponent B. (dimensionless) | + +#### Returns + +Unsaturated hydraulic conductivity at the supplied water content. (mm/h) + +!!! warning + + The equation does not include residual water content. + +### `calc_ptf_saxton2006_gravel` {#function-calc_ptf_saxton2006_gravel} + +Adjust matric-soil properties for gravel content. + +```cpp +[[nodiscard]] +inline Saxton2006GravelResult calc_ptf_saxton2006_gravel(double gravel_weight_fraction, double matric_density, double plant_available_water, double saturated_conductivity) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `gravel_weight_fraction` | Gravel mass fraction of the bulk soil. (g/g) | +| `matric_density` | Dry bulk density of the fine-earth matric soil. (g/cm^3) | +| `plant_available_water` | Plant-available water of the matric soil. (m^3/m^3) | +| `saturated_conductivity` | Saturated hydraulic conductivity of the matric soil. (mm/h) | + +#### Returns + +A `Saxton2006GravelResult` value. + +!!! note + + Gravel comprises particles larger than 2 mm. + +!!! note + + Gravel density is fixed at 2.65 g/cm^3 in the source equations. + +!!! warning + + The conductivity correction does not represent extra macropores sometimes found in gravelly soils. + +### `calc_ptf_saxton2006_salinity` {#function-calc_ptf_saxton2006_salinity} + +Estimate saturated and moisture-adjusted osmotic potential. + +```cpp +[[nodiscard]] +inline Saxton2006SalinityResult calc_ptf_saxton2006_salinity(double electrical_conductivity, double theta, double theta_s) +``` + +#### Parameters + +| Name | Description | +| --- | --- | +| `electrical_conductivity` | Electrical conductivity of a saturated soil extract. (dS/m) | +| `theta` | Current volumetric water content. (m^3/m^3) | +| `theta_s` | Saturated volumetric water content. (m^3/m^3) | + +#### Returns + +A `Saxton2006SalinityResult` value. + +!!! note + + The moisture adjustment assumes dissolved salt quantity remains near constant as water content falls. + +!!! warning + + Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity effects. diff --git a/docs/src/reference/python/index.md b/docs/src/reference/python/index.md index 14af279..d5f4cb6 100644 --- a/docs/src/reference/python/index.md +++ b/docs/src/reference/python/index.md @@ -21,9 +21,11 @@ ptfkit's Python API is organized around public source modules. - [`ptfkit.jabro1992`](jabro1992.md) — Jabro (1992), United States. - [`ptfkit.li2007`](li2007.md) — Li et al. (2007), Fengqiu County, North China Plain, China. - [`ptfkit.mayr1999`](mayr1999.md) — Mayr and Jarvis (1999) modified Brooks-Corey water-retention parameter PTFs. +- [`ptfkit.oosterveld1980`](oosterveld1980.md) — Oosterveld and Chang (1980), soil-moisture retention from texture and depth. - [`ptfkit.pidgeon1972`](pidgeon1972.md) — Pidgeon (1972), available-water regressions for ferrallitic soils in Uganda. - [`ptfkit.puckett1985`](puckett1985.md) — Puckett et al. (1985), Alabama Lower Coastal Plain Ultisols. - [`ptfkit.rawls1982`](rawls1982.md) — Rawls et al. (1982), water-retention regressions for soils across the USA. +- [`ptfkit.saxton2006`](saxton2006.md) — Saxton and Rawls (2006), soil water characteristics from texture and organic matter. - [`ptfkit.tiwary2014`](tiwary2014.md) — Tiwary et al. (2014), hydraulic PTFs for two major soil regions of India. - [`ptfkit.varallyai1982`](varallyai1982.md) — Varallyai et al. (1982), water-retention parameter regressions for Hungarian soils. - [`ptfkit.vereecken1989`](vereecken1989.md) — Vereecken et al. soil-moisture retention regressions for Belgian soils. diff --git a/docs/src/reference/python/oosterveld1980.md b/docs/src/reference/python/oosterveld1980.md new file mode 100644 index 0000000..3f7436d --- /dev/null +++ b/docs/src/reference/python/oosterveld1980.md @@ -0,0 +1,8 @@ +--- +# @generated by ptfkit-codegen; DO NOT EDIT. + +title: Python module ptfkit.oosterveld1980 +nav-title: ptfkit.oosterveld1980 +--- + +::: ptfkit.oosterveld1980 diff --git a/docs/src/reference/python/saxton2006.md b/docs/src/reference/python/saxton2006.md new file mode 100644 index 0000000..913a2fd --- /dev/null +++ b/docs/src/reference/python/saxton2006.md @@ -0,0 +1,8 @@ +--- +# @generated by ptfkit-codegen; DO NOT EDIT. + +title: Python module ptfkit.saxton2006 +nav-title: ptfkit.saxton2006 +--- + +::: ptfkit.saxton2006 diff --git a/specs/functions/oosterveld1980.yaml b/specs/functions/oosterveld1980.yaml new file mode 100644 index 0000000..7612795 --- /dev/null +++ b/specs/functions/oosterveld1980.yaml @@ -0,0 +1,360 @@ +source: + summary: Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + citation_apa: >- + Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory + determinations of soil texture and moisture retention. Canadian Agricultural + Engineering, 22, 149-151. + doi: null +scope: + territory: Southern Alberta, Canada + dataset: >- + 1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research + Station laboratory; the field-capacity tension regression used 134 samples. +$defs: + clay: + name: clay + symbol: C + unit: '% mass' + domain: null + description: Clay content in percent by weight. + clay_field_capacity: + name: clay + symbol: C + unit: '% mass' + domain: 2 <= value <= 72 + description: >- + Clay content in percent by weight; the field-capacity tension experiment + covered 2% to 72% clay. + sand: + name: sand + symbol: S + unit: '% mass' + domain: null + description: Sand content in percent by weight. + mean_depth: + name: mean_depth + symbol: D + unit: cm + domain: 8 <= value <= 180 + description: Mean depth of the soil sample. + tension: + name: tension + symbol: T + unit: kPa + domain: 10 <= value <= 1500 + description: Soil-moisture tension. +functions: + - name: calc_ptf_oosterveld1980_field_capacity_tension + status: implemented + public_api: + name: calc_ptf_oosterveld1980_field_capacity_tension + result_class: null + summary: Estimate field-capacity tension from clay content. + scope: + prediction_target: >- + Tension at which pressure-plate moisture content equals field capacity + determined by the cylinder method. + models: + h_theta: Field-capacity tension point estimate + k_h: null + inputs: + - $ref: "#/$defs/clay_field_capacity" + outputs: + type: scalar + name: field_capacity_tension + symbol: T_Pr + unit: kPa + domain: value > 0 + description: Predicted tension corresponding to cylinder-method field capacity. + golden_tests: + - id: table_1_loamy_sand + inputs: + clay: 6.6 + expected: + field_capacity_tension: 11.8540994164288 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: >- + Direct evaluation of Equation 1; Table I reports the value rounded to + 11.8 kPa. + edge_cases: + - id: nan_clay + inputs: + clay: .nan + expected_behavior: Propagate NaN to the output. + notes: Reviewed IEEE-754 and NumPy-compatible numeric policy. + documentation: + notes: + - Equation 1 reports r = 0.67 and n = 134. + warnings: [] + implementation: + variables: + - name: field_capacity_tension + expr: 5.356 * clay ^ 0.421 + + - name: calc_ptf_oosterveld1980_retention + status: implemented + public_api: + name: calc_ptf_oosterveld1980_retention + result_class: null + summary: Estimate gravimetric soil-moisture content from texture, depth, and tension. + scope: + prediction_target: Gravimetric soil-moisture content at a specified tension. + models: + h_theta: Empirical power-law moisture-retention relation + k_h: null + inputs: + - $ref: "#/$defs/clay" + - $ref: "#/$defs/sand" + - $ref: "#/$defs/mean_depth" + - $ref: "#/$defs/tension" + outputs: + type: scalar + name: moisture_content + symbol: theta + unit: '% mass' + domain: null + description: Predicted soil-moisture content in percent by weight. + golden_tests: + - id: table_1_loamy_sand_inputs + inputs: + clay: 6.6 + sand: 86.3 + mean_depth: 105 + tension: 11.8 + expected: + moisture_content: 8.2782468100413 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: >- + Direct evaluation of the corrected Equation 2 at the Table I loamy-sand + inputs and its rounded field-capacity tension. Table I instead reports + the separate Equation 3 field-capacity prediction. + edge_cases: + - id: nan_tension + inputs: + clay: 6.6 + sand: 86.3 + mean_depth: 105 + tension: .nan + expected_behavior: Propagate NaN to the output. + notes: Reviewed IEEE-754 and NumPy-compatible numeric policy. + documentation: + notes: + - Equation 2 reports r = 0.96 and n = 1,137. + warnings: + - The paper reports slight inaccuracy at very high clay content and high tension. + implementation: + variables: + - name: moisture_content + expr: (35.367 + 0.644 * clay - 0.251 * sand - 0.045 * mean_depth) * tension ^ (-0.190) + + - name: calc_ptf_oosterveld1980_field_capacity + status: implemented + public_api: + name: calc_ptf_oosterveld1980_field_capacity + result_class: null + summary: Estimate gravimetric soil-moisture content at field capacity. + scope: + prediction_target: Gravimetric soil-moisture content at field capacity. + models: + h_theta: Field-capacity point estimate derived from Equations 1 and 2 + k_h: null + inputs: + - $ref: "#/$defs/clay_field_capacity" + - $ref: "#/$defs/sand" + - $ref: "#/$defs/mean_depth" + outputs: + type: scalar + name: field_capacity_moisture + symbol: theta_fc + unit: '% mass' + domain: null + description: Predicted soil-moisture content at field capacity in percent by weight. + golden_tests: + - id: table_1_loamy_sand + inputs: + clay: 6.6 + sand: 86.3 + mean_depth: 105 + expected: + field_capacity_moisture: 8.14707947394088 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: >- + Direct evaluation of corrected Equation 3; Table I reports the value + rounded to 8.1%. + edge_cases: + - id: nan_clay + inputs: + clay: .nan + sand: 86.3 + mean_depth: 105 + expected_behavior: Propagate NaN to the output. + notes: Reviewed IEEE-754 and NumPy-compatible numeric policy. + documentation: + notes: + - >- + Equation 3 is obtained by substituting the Equation 1 field-capacity + tension into Equation 2. + warnings: [] + implementation: + variables: + - name: field_capacity_moisture + expr: (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * clay ^ (-0.080) + + - name: calc_ptf_oosterveld1980_wilting_point + status: implemented + public_api: + name: calc_ptf_oosterveld1980_wilting_point + result_class: null + summary: Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + scope: + prediction_target: Gravimetric soil-moisture content at 1500 kPa. + models: + h_theta: Wilting-point estimate at 1500 kPa + k_h: null + inputs: + - $ref: "#/$defs/clay" + - $ref: "#/$defs/sand" + - $ref: "#/$defs/mean_depth" + outputs: + type: scalar + name: wilting_point_moisture + symbol: theta_1500_kPa + unit: '% mass' + domain: null + description: Predicted soil-moisture content at 1500 kPa in percent by weight. + golden_tests: + - id: table_1_loamy_sand + inputs: + clay: 6.6 + sand: 86.3 + mean_depth: 105 + expected: + wilting_point_moisture: 1.3942 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: >- + Direct evaluation of Equation 4; Table I reports the value rounded to + 1.4%. + edge_cases: + - id: nan_sand + inputs: + clay: 6.6 + sand: .nan + mean_depth: 105 + expected_behavior: Propagate NaN to the output. + notes: Reviewed IEEE-754 and NumPy-compatible numeric policy. + documentation: + notes: + - Equation 4 reports r = 0.96 and n = 298. + - The paper takes moisture content at 1500 kPa as the wilting point. + warnings: [] + implementation: + variables: + - name: wilting_point_moisture + expr: 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth + + - name: calc_ptf_oosterveld1980_available_water + status: implemented + public_api: + name: calc_ptf_oosterveld1980_available_water + result_class: null + summary: Estimate available gravimetric soil moisture between field capacity and wilting point. + scope: + prediction_target: >- + Difference between gravimetric moisture content at field capacity and at + the 1500 kPa wilting point. + models: + h_theta: Available-moisture point estimate from Equations 3 and 4 + k_h: null + inputs: + - $ref: "#/$defs/clay_field_capacity" + - $ref: "#/$defs/sand" + - $ref: "#/$defs/mean_depth" + outputs: + type: scalar + name: available_moisture + symbol: null + unit: '% mass' + domain: null + description: >- + Predicted field-capacity moisture minus predicted wilting-point moisture, + in percentage points by weight. + golden_tests: + - id: table_1_loamy_sand + inputs: + clay: 6.6 + sand: 86.3 + mean_depth: 105 + expected: + available_moisture: 6.75287947394088 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: >- + Direct evaluation of Equations 3 and 4 followed by subtraction; Table I + reports the value rounded to 6.7%. + edge_cases: + - id: nan_mean_depth + inputs: + clay: 6.6 + sand: 86.3 + mean_depth: .nan + expected_behavior: Propagate NaN to the output. + notes: Reviewed IEEE-754 and NumPy-compatible numeric policy. + documentation: + notes: + - The paper defines available soil water by subtracting Equation 4 from Equation 3. + warnings: [] + implementation: + variables: + - name: field_capacity_moisture + expr: (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * clay ^ (-0.080) + - name: wilting_point_moisture + expr: 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth + - name: available_moisture + expr: field_capacity_moisture - wilting_point_moisture + +scientific_notes: | + # Oosterveld and Chang (1980) + + ## Supported models + + - Equation 1 relates clay content to the tension corresponding to cylinder-method + field capacity. Equation 2 is a general gravimetric moisture-retention relation. + - Equation 3 substitutes Equation 1 into Equation 2 to estimate field-capacity + moisture. Equation 4 estimates moisture at 1500 kPa, which the paper treats as + wilting point. Available moisture is Equation 3 minus Equation 4. + + ## Review decisions + + - The prepared source correction overrides damaged OCR in Equation 2: its tension + exponent is `-0.190`, not the transcribed `-0.180`. + - The same correction establishes the Equation 3 clay factor as `C^(-0.080)`, not + the transcribed `C^(-0.000)`. This exponent follows from substituting the + Equation 1 tension relation into corrected Equation 2. + - Preserve all moisture outputs as percent by weight. The prepared source + explicitly excludes a bulk-density factor and percent-to-fraction conversion. + - Use volume 22 in the citation, following the prepared source's canonical + provenance; the appended transcription prints volume 21. + - Golden values are unrounded direct evaluations of the printed or corrected + equations. Rounded Table I predictions provide source-native cross-checks. + + ## Documented limitations + + - Apply these relationships only to southern Alberta soils or soils with similar + organic matter content and structure. The paper reports surface-soil organic + carbon of about 1.6% to 1.8% for the source area. + - Equation 2 is slightly inaccurate at the combination of very high clay content + and high tension. The separate 1500 kPa Equation 4 was fitted across the full + texture range to address that limitation at wilting point. + - Field water retained in macropores depends strongly on structure; the paper + recommends site measurement when an accurate field-capacity estimate is needed. + + ## Numeric policy + + - Evaluate in `f64` without rounding intermediate or returned values. Propagate + NaN and infinities according to IEEE-754 and NumPy-compatible arithmetic. + - Treat documented input domains as applicability guidance rather than runtime + clamps. Do not clamp outputs or finite out-of-domain inputs. diff --git a/specs/functions/saxton2006.yaml b/specs/functions/saxton2006.yaml new file mode 100644 index 0000000..1402473 --- /dev/null +++ b/specs/functions/saxton2006.yaml @@ -0,0 +1,639 @@ +source: + summary: Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + citation_apa: >- + Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic + estimates by texture and organic matter for hydrologic solutions. Soil + Science Society of America Journal, 70(5), 1569-1578. + doi: + identifier: 10.2136/ssaj2005.0117 + url: https://doi.org/10.2136/ssaj2005.0117 +scope: + territory: United States + dataset: >- + 1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National + Soil Characterization database after exclusions from 2,149 samples. +functions: + - name: calc_ptf_saxton2006 + status: implemented + public_api: + name: calc_ptf_saxton2006 + result_class: Saxton2006PTFResult + summary: >- + Estimate soil water characteristics from sand, clay, and organic + matter. + scope: + prediction_target: >- + Water contents at 1500, 33, and 0 kPa, plant-available water, air-entry + tension, retention coefficients, normal density, and saturated + hydraulic conductivity. + models: + h_theta: Campbell power function with a linear wet-range segment + k_h: Campbell unsaturated conductivity model + inputs: + - name: sand + symbol: S + unit: g/g + domain: 0 <= value <= 1 + description: Sand mass fraction of the fine-earth soil. + - name: clay + symbol: C + unit: g/g + domain: 0 <= value <= 0.60 + description: Clay mass fraction of the fine-earth soil. + - name: organic_matter + symbol: OM + unit: '% mass' + domain: 0 <= value <= 8 + description: Organic matter content on a mass percentage basis. + outputs: + type: record + name: Saxton2006PTFResult + fields: + - name: theta_1500 + symbol: theta_1500 + unit: m^3/m^3 + domain: value > 0 + description: Volumetric water content at 1500 kPa matric tension. + - name: theta_33 + symbol: theta_33 + unit: m^3/m^3 + domain: value > 0 + description: Volumetric water content at 33 kPa matric tension. + - name: theta_s + symbol: theta_s + unit: m^3/m^3 + domain: value > 0 + description: Saturated volumetric water content at normal density. + - name: plant_available_water + symbol: PAW + unit: m^3/m^3 + domain: value >= 0 + description: Difference between the 33 and 1500 kPa water contents. + - name: air_entry_tension + symbol: psi_e + unit: kPa + domain: value > 0 + description: Air-entry or bubbling-pressure tension. + - name: retention_a + symbol: A + unit: kPa + domain: value > 0 + description: Coefficient A of the dry-range moisture-tension curve. + - name: retention_b + symbol: B + unit: dimensionless + domain: value > 0 + description: Exponent B of the dry-range moisture-tension curve. + - name: conductivity_lambda + symbol: lambda + unit: dimensionless + domain: value > 0 + description: Inverse of retention exponent B. + - name: saturated_conductivity + symbol: K_s + unit: mm/h + domain: value >= 0 + description: Saturated hydraulic conductivity of the matric soil. + - name: normal_density + symbol: rho_N + unit: g/cm^3 + domain: value > 0 + description: Normal dry bulk density assuming particle density 2.65 g/cm^3. + golden_tests: + - id: table_3_sand + inputs: + sand: 0.88 + clay: 0.05 + organic_matter: 2.5 + expected: + theta_1500: 0.05022058 + theta_33: 0.10282792364858702 + theta_s: 0.46172240764858724 + plant_available_water: 0.05260734364858702 + air_entry_tension: 0.5986789729513293 + retention_a: 0.00018076452552206025 + retention_b: 5.325903100453899 + conductivity_lambda: 0.18776158355467926 + saturated_conductivity: 108.1478278507403 + normal_density: 1.4264356197312436 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: >- + Calculated directly from Table 1. The source-native rounded Table 3 + values are 0.05, 0.10, 0.46, 0.05, 108.1 mm/h, and 1.43 g/cm^3 for + theta_1500, theta_33, theta_s, PAW, K_s, and rho_N, respectively. + edge_cases: [] + documentation: + notes: + - Sand and clay are decimal mass fractions, while organic matter is a percentage. + - The 1500 and 33 kPa values are also termed wilting point and field capacity. + warnings: + - Do not apply the regression above 8% organic matter or 60% clay. + - Sand and clay fractions must describe one soil and therefore must sum to at most 1. + - These statistical-average estimates should be calibrated to local measurements when available. + implementation: + variables: + - name: theta_1500_preliminary + expr: -0.024 * sand + 0.487 * clay + 0.006 * organic_matter + 0.005 * sand * organic_matter - 0.013 * clay * organic_matter + 0.068 * sand * clay + 0.031 + - name: theta_1500 + expr: theta_1500_preliminary + 0.14 * theta_1500_preliminary - 0.02 + - name: theta_33_preliminary + expr: -0.251 * sand + 0.195 * clay + 0.011 * organic_matter + 0.006 * sand * organic_matter - 0.027 * clay * organic_matter + 0.452 * sand * clay + 0.299 + - name: theta_33 + expr: theta_33_preliminary + 1.283 * theta_33_preliminary ^ 2 - 0.374 * theta_33_preliminary - 0.015 + - name: theta_s_minus_33_preliminary + expr: 0.278 * sand + 0.034 * clay + 0.022 * organic_matter - 0.018 * sand * organic_matter - 0.027 * clay * organic_matter - 0.584 * sand * clay + 0.078 + - name: theta_s_minus_33 + expr: theta_s_minus_33_preliminary + 0.636 * theta_s_minus_33_preliminary - 0.107 + - name: theta_s + expr: theta_33 + theta_s_minus_33 - 0.097 * sand + 0.043 + - name: plant_available_water + expr: theta_33 - theta_1500 + - name: air_entry_preliminary + expr: -21.67 * sand - 27.93 * clay - 81.97 * theta_s_minus_33_preliminary + 71.12 * sand * theta_s_minus_33_preliminary + 8.29 * clay * theta_s_minus_33_preliminary + 14.05 * sand * clay + 27.16 + - name: air_entry_tension + expr: air_entry_preliminary + 0.02 * air_entry_preliminary ^ 2 - 0.113 * air_entry_preliminary - 0.70 + - name: retention_b + expr: (ln(1500) - ln(33)) / (ln(theta_33) - ln(theta_1500)) + - name: retention_a + expr: exp(ln(33) + retention_b * ln(theta_33)) + - name: conductivity_lambda + expr: 1 / retention_b + - name: saturated_conductivity + expr: 1930 * (theta_s - theta_33) ^ (3 - conductivity_lambda) + - name: normal_density + expr: (1 - theta_s) * 2.65 + + - name: calc_ptf_saxton2006_density + status: implemented + public_api: + name: calc_density_adjustment_saxton2006 + result_class: Saxton2006DensityResult + summary: Adjust Saxton and Rawls water characteristics for soil density. + scope: + prediction_target: Density-adjusted saturation and 33 kPa water contents. + models: + h_theta: Density adjustment to the point estimates + k_h: null + inputs: + - name: normal_density + symbol: rho_N + unit: g/cm^3 + domain: value > 0 + description: Normal dry bulk density estimated by the base model. + - name: theta_s + symbol: theta_s + unit: m^3/m^3 + domain: value > 0 + description: Saturated water content at normal density. + - name: theta_33 + symbol: theta_33 + unit: m^3/m^3 + domain: value > 0 + description: Water content at 33 kPa and normal density. + - name: density_factor + symbol: DF + unit: dimensionless + domain: 0.9 <= value <= 1.3 + description: Multiplicative adjustment to normal density. + outputs: + type: record + name: Saxton2006DensityResult + fields: + - name: adjusted_density + symbol: rho_DF + unit: g/cm^3 + domain: value > 0 + description: Density after applying the density factor. + - name: adjusted_theta_s + symbol: theta_S-DF + unit: m^3/m^3 + domain: value > 0 + description: Saturated water content after the density adjustment. + - name: adjusted_theta_33 + symbol: theta_33-DF + unit: m^3/m^3 + domain: value > 0 + description: Water content at 33 kPa after the density adjustment. + - name: adjusted_theta_s_minus_33 + symbol: theta_(S-33)DF + unit: m^3/m^3 + domain: value >= 0.005 + description: Density-adjusted difference between saturation and 33 kPa. + golden_tests: + - id: table_3_sand_compacted + inputs: + normal_density: 1.4264356197312436 + theta_s: 0.46172240764858724 + theta_33: 0.10282792364858702 + density_factor: 1.1 + expected: + adjusted_density: 1.569079181704368 + adjusted_theta_s: 0.407894648413446 + adjusted_theta_33: 0.09206237180155877 + adjusted_theta_s_minus_33: 0.3158322766118873 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: Calculated directly from Table 1, Equations 7-10. + edge_cases: [] + documentation: + notes: + - The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + warnings: + - The source recommends density factors only from 0.9 to 1.3. + implementation: + variables: + - name: adjusted_density + expr: normal_density * density_factor + - name: adjusted_theta_s + expr: 1 - adjusted_density / 2.65 + - name: adjusted_theta_33 + expr: theta_33 - 0.2 * (theta_s - adjusted_theta_s) + - name: adjusted_theta_s_minus_33 + expr: max(adjusted_theta_s - adjusted_theta_33, 0.005) + + - name: calc_ptf_saxton2006_tension_dry + status: implemented + public_api: + name: calc_tension_dry_saxton2006 + result_class: null + summary: Estimate matric tension in the 1500 to 33 kPa segment. + scope: + prediction_target: Matric tension for water content between theta_1500 and theta_33. + models: + h_theta: Campbell power function + k_h: null + inputs: + - name: theta + symbol: theta + unit: m^3/m^3 + domain: theta_1500 <= value <= theta_33 + description: Volumetric water content. + - name: theta_1500 + symbol: theta_1500 + unit: m^3/m^3 + domain: value > 0 + description: Volumetric water content at 1500 kPa. + - name: theta_33 + symbol: theta_33 + unit: m^3/m^3 + domain: value > theta_1500 + description: Volumetric water content at 33 kPa. + outputs: + type: scalar + name: tension + symbol: psi_theta + unit: kPa + domain: 33 <= value <= 1500 + description: Matric tension at the supplied water content. + golden_tests: + - id: table_3_sand_midpoint + inputs: + theta: 0.07652425182429351 + theta_1500: 0.05022058 + theta_33: 0.10282792364858702 + expected: + tension: 159.18094591362183 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: Calculated directly from Table 1, Equations 11, 14, and 15. + edge_cases: [] + documentation: + notes: [] + warnings: + - Use only for the 1500 to 33 kPa segment defined by the source. + implementation: + variables: + - name: retention_b + expr: (ln(1500) - ln(33)) / (ln(theta_33) - ln(theta_1500)) + - name: retention_a + expr: exp(ln(33) + retention_b * ln(theta_33)) + - name: tension + expr: retention_a * theta ^ (-retention_b) + + - name: calc_ptf_saxton2006_tension_wet + status: implemented + public_api: + name: calc_tension_wet_saxton2006 + result_class: null + summary: Estimate matric tension in the 33 kPa to air-entry segment. + scope: + prediction_target: Matric tension for water content between theta_33 and theta_s. + models: + h_theta: Linear wet-range segment + k_h: null + inputs: + - name: theta + symbol: theta + unit: m^3/m^3 + domain: theta_33 <= value <= theta_s + description: Volumetric water content. + - name: theta_33 + symbol: theta_33 + unit: m^3/m^3 + domain: value > 0 + description: Volumetric water content at 33 kPa. + - name: theta_s + symbol: theta_s + unit: m^3/m^3 + domain: value > theta_33 + description: Saturated volumetric water content. + - name: air_entry_tension + symbol: psi_e + unit: kPa + domain: 0 <= value < 33 + description: Air-entry tension estimated by the base model. + outputs: + type: scalar + name: tension + symbol: psi_theta + unit: kPa + domain: air_entry_tension <= value <= 33 + description: Matric tension at the supplied water content. + golden_tests: + - id: table_3_sand_theta_0_2 + inputs: + theta: 0.2 + theta_33: 0.10282792364858702 + theta_s: 0.46172240764858724 + air_entry_tension: 0.5986789729513293 + expected: + tension: 24.227216407352152 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: Calculated directly from Table 1, Equation 12. + edge_cases: [] + documentation: + notes: + - At tensions below air entry, Equation 13 fixes water content at theta_s. + warnings: + - Use only for the 33 kPa to air-entry segment defined by the source. + implementation: + variables: + - name: tension + expr: 33 - (theta - theta_33) * (33 - air_entry_tension) / (theta_s - theta_33) + + - name: calc_ptf_saxton2006_conductivity + status: implemented + public_api: + name: calc_conductivity_saxton2006 + result_class: null + summary: Estimate unsaturated hydraulic conductivity from water content. + scope: + prediction_target: Unsaturated hydraulic conductivity of the matric soil. + models: + h_theta: null + k_h: Campbell unsaturated conductivity model + inputs: + - name: theta + symbol: theta + unit: m^3/m^3 + domain: 0 < value <= theta_s + description: Volumetric water content. + - name: theta_s + symbol: theta_s + unit: m^3/m^3 + domain: value > 0 + description: Saturated volumetric water content. + - name: saturated_conductivity + symbol: K_s + unit: mm/h + domain: value >= 0 + description: Saturated hydraulic conductivity of the matric soil. + - name: conductivity_lambda + symbol: lambda + unit: dimensionless + domain: value > 0 + description: Inverse of retention exponent B. + outputs: + type: scalar + name: conductivity + symbol: K_theta + unit: mm/h + domain: value >= 0 + description: Unsaturated hydraulic conductivity at the supplied water content. + golden_tests: + - id: table_3_sand_theta_0_3 + inputs: + theta: 0.3 + theta_s: 0.46172240764858724 + saturated_conductivity: 108.1478278507403 + conductivity_lambda: 0.18776158355467926 + expected: + conductivity: 0.3003203142764693 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: Calculated directly from corrected Table 1, Equation 17. + edge_cases: [] + documentation: + notes: [] + warnings: + - The equation does not include residual water content. + implementation: + variables: + - name: conductivity + expr: saturated_conductivity * (theta / theta_s) ^ (3 + 2 / conductivity_lambda) + + - name: calc_ptf_saxton2006_gravel + status: implemented + public_api: + name: calc_gravel_adjustment_saxton2006 + result_class: Saxton2006GravelResult + summary: Adjust matric-soil properties for gravel content. + scope: + prediction_target: Gravel volume, bulk density, available water, and saturated conductivity. + models: + h_theta: Gravel reduction of plant-available water + k_h: Peck-Watson conductivity reduction + inputs: + - name: gravel_weight_fraction + symbol: R_w + unit: g/g + domain: 0 <= value < 1 + description: Gravel mass fraction of the bulk soil. + - name: matric_density + symbol: rho + unit: g/cm^3 + domain: 0 < value <= 2.65 + description: Dry bulk density of the fine-earth matric soil. + - name: plant_available_water + symbol: PAW + unit: m^3/m^3 + domain: value >= 0 + description: Plant-available water of the matric soil. + - name: saturated_conductivity + symbol: K_s + unit: mm/h + domain: value >= 0 + description: Saturated hydraulic conductivity of the matric soil. + outputs: + type: record + name: Saxton2006GravelResult + fields: + - name: gravel_volume_fraction + symbol: R_v + unit: m^3/m^3 + domain: 0 <= value < 1 + description: Gravel volume fraction of the bulk soil. + - name: bulk_density + symbol: rho_B + unit: g/cm^3 + domain: value > 0 + description: Dry bulk density of matric soil plus gravel. + - name: bulk_plant_available_water + symbol: PAW_B + unit: m^3/m^3 + domain: value >= 0 + description: Plant-available water on a bulk-soil volume basis. + - name: bulk_saturated_conductivity + symbol: K_b + unit: mm/h + domain: value >= 0 + description: Saturated conductivity after the gravel reduction. + golden_tests: + - id: table_3_sand_twenty_percent_gravel + inputs: + gravel_weight_fraction: 0.2 + matric_density: 1.4264356197312436 + plant_available_water: 0.05260734364858702 + saturated_conductivity: 108.1478278507403 + expected: + gravel_volume_fraction: 0.11860834455314037 + bulk_density: 1.5715605653291098 + bulk_plant_available_water: 0.04636767370708995 + bulk_saturated_conductivity: 97.54880510583706 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: Calculated directly from Table 1, Equations 19-22. + edge_cases: [] + documentation: + notes: + - Gravel comprises particles larger than 2 mm. + - Gravel density is fixed at 2.65 g/cm^3 in the source equations. + warnings: + - The conductivity correction does not represent extra macropores sometimes found in gravelly soils. + implementation: + variables: + - name: density_ratio + expr: matric_density / 2.65 + - name: gravel_volume_fraction + expr: density_ratio * gravel_weight_fraction / (1 - gravel_weight_fraction * (1 - density_ratio)) + - name: bulk_density + expr: matric_density * (1 - gravel_volume_fraction) + gravel_volume_fraction * 2.65 + - name: bulk_plant_available_water + expr: plant_available_water * (1 - gravel_volume_fraction) + - name: bulk_saturated_conductivity + expr: saturated_conductivity * (1 - gravel_volume_fraction) / (1 - gravel_volume_fraction * (1 - 3 * density_ratio / 2)) + + - name: calc_ptf_saxton2006_salinity + status: implemented + public_api: + name: calc_osmotic_potential_saxton2006 + result_class: Saxton2006SalinityResult + summary: Estimate saturated and moisture-adjusted osmotic potential. + scope: + prediction_target: Osmotic potential from saturated-extract electrical conductivity. + models: + h_theta: Matric-plus-osmotic tension adjustment + k_h: null + inputs: + - name: electrical_conductivity + symbol: EC + unit: dS/m + domain: value >= 0 + description: Electrical conductivity of a saturated soil extract. + - name: theta + symbol: theta + unit: m^3/m^3 + domain: 0 < value <= theta_s + description: Current volumetric water content. + - name: theta_s + symbol: theta_s + unit: m^3/m^3 + domain: value > 0 + description: Saturated volumetric water content. + outputs: + type: record + name: Saxton2006SalinityResult + fields: + - name: saturated_osmotic_potential + symbol: psi_O + unit: kPa + domain: value >= 0 + description: Osmotic potential at saturation. + - name: osmotic_potential + symbol: psi_Otheta + unit: kPa + domain: value >= 0 + description: Osmotic potential at the supplied water content. + golden_tests: + - id: saline_partly_saturated_soil + inputs: + electrical_conductivity: 4.0 + theta: 0.3 + theta_s: 0.46172240764858724 + expected: + saturated_osmotic_potential: 144.0 + osmotic_potential: 221.6267556713219 + rtol: 1.0e-12 + atol: 1.0e-12 + notes: Calculated directly from Table 1, Equations 23 and 24. + edge_cases: [] + documentation: + notes: + - The moisture adjustment assumes dissolved salt quantity remains near constant as water content falls. + warnings: + - Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity effects. + implementation: + variables: + - name: saturated_osmotic_potential + expr: 36 * electrical_conductivity + - name: osmotic_potential + expr: theta_s / theta * saturated_osmotic_potential + +scientific_notes: | + # Saxton and Rawls (2006) + + ## Supported models + + The source combines regressions for water contents at 1500 and 33 kPa, + saturation-minus-33 kPa, and air-entry tension with a three-segment + moisture-tension relationship. The dry segment is a Campbell power function, + the intermediate segment is linear, and water content is constant at + saturation below air entry. Saturated conductivity follows Equation 16 and + unsaturated conductivity follows the Campbell relationship in Equation 17. + Separate transformations represent density, gravel, and salinity effects. + + ## Review decisions + + Sand and clay are represented as decimal mass fractions because that + interpretation reproduces Table 3; organic matter remains a mass percentage. + The prepared source resolves the preliminary-variable OCR collisions in + Equations 1-4. In particular, the air-entry regression uses the preliminary + saturation-minus-33 kPa value. The plus sign in the Equation 2 lack-of-fit + adjustment is retained because it reproduces every rounded sand-texture value + supplied by Table 3, whereas the damaged transcription's minus sign does not. + + The prepared correction supplies the parentheses in + `B = [ln(1500)-ln(33)] / [ln(theta_33)-ln(theta_1500)]`, distinguishes gravel + mass fraction `R_w` from volume fraction `R_v`, retains the Equation 16 + exponent `3-lambda`, and corrects the Equation 17 exponent to + `3+2/lambda`. Equation 24 is represented as `theta_s/theta` times saturated + osmotic potential, consistent with the source's statement that concentration + and osmotic potential increase as soil water decreases from saturation. + + ## Documented limitations + + The regression data excluded bulk densities below 1.0 or above 1.8 g/cm^3, + organic matter above 8%, and clay above 60%. Only A-horizon samples were used + to fit the regressions. The estimates are statistical averages, and the paper + recommends calibration to site-specific data when available. The gravel + conductivity model treats gravel as nonconducting inclusions and does not + account for associated macropores. Salinity chemistry may depart from the + constant-dissolved-salt approximation. + + ## Blockers + + None. The prepared source handoff explicitly resolves the material OCR + collisions, and the Table 3 sand case provides source-native numerical + validation for the base predictive sequence. diff --git a/targets/ptfkit-native/cmake/ptfkitModules.cmake b/targets/ptfkit-native/cmake/ptfkitModules.cmake index 112d3d5..dfd6613 100644 --- a/targets/ptfkit-native/cmake/ptfkitModules.cmake +++ b/targets/ptfkit-native/cmake/ptfkitModules.cmake @@ -13,9 +13,11 @@ set(PTFKIT_CPP_MODULES "${CMAKE_CURRENT_LIST_DIR}/../cpp/jabro1992.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/li2007.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/mayr1999.cppm" + "${CMAKE_CURRENT_LIST_DIR}/../cpp/oosterveld1980.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/pidgeon1972.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/puckett1985.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/rawls1982.cppm" + "${CMAKE_CURRENT_LIST_DIR}/../cpp/saxton2006.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/tiwary2014.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/varallyai1982.cppm" "${CMAKE_CURRENT_LIST_DIR}/../cpp/vereecken1989.cppm" diff --git a/targets/ptfkit-native/cpp/oosterveld1980.cppm b/targets/ptfkit-native/cpp/oosterveld1980.cppm new file mode 100644 index 0000000..d9734f3 --- /dev/null +++ b/targets/ptfkit-native/cpp/oosterveld1980.cppm @@ -0,0 +1,118 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +module; +#include + +export module ptfkit.oosterveld1980; + +/** + * @brief Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + * + * @details Source publication: + * Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory determinations of + * soil texture and moisture retention. Canadian Agricultural Engineering, 22, 149-151. + * + * @remark Geographic scope: + * Southern Alberta, Canada + * + * @remark Calibration dataset: + * 1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research Station + * laboratory; the field-capacity tension regression used 134 samples. + */ + +export namespace ptfkit::oosterveld1980 { + +/** + * @brief Estimate field-capacity tension from clay content. + * @param clay Clay content in percent by weight; the field-capacity tension experiment covered + * 2% to 72% clay. (% mass) + * @return Predicted tension corresponding to cylinder-method field capacity. (kPa) + * + * @details Prediction target: + * Tension at which pressure-plate moisture content equals field capacity determined by the + * cylinder method. + * @note Equation 1 reports r = 0.67 and n = 134. + */ +[[nodiscard]] +inline double calc_ptf_oosterveld1980_field_capacity_tension(double clay) { + return 5.356 * std::pow(clay, 0.421); +} + +/** + * @brief Estimate gravimetric soil-moisture content from texture, depth, and tension. + * @param clay Clay content in percent by weight. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @param tension Soil-moisture tension. (kPa) + * @return Predicted soil-moisture content in percent by weight. (% mass) + * + * @details Prediction target: + * Gravimetric soil-moisture content at a specified tension. + * @note Equation 2 reports r = 0.96 and n = 1,137. + * @warning The paper reports slight inaccuracy at very high clay content and high tension. + */ +[[nodiscard]] +inline double calc_ptf_oosterveld1980_retention(double clay, double sand, double mean_depth, + double tension) { + return (35.367 + 0.644 * clay - 0.251 * sand - 0.045 * mean_depth) * std::pow(tension, -0.190); +} + +/** + * @brief Estimate gravimetric soil-moisture content at field capacity. + * @param clay Clay content in percent by weight; the field-capacity tension experiment covered + * 2% to 72% clay. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @return Predicted soil-moisture content at field capacity in percent by weight. (% mass) + * + * @details Prediction target: + * Gravimetric soil-moisture content at field capacity. + * @note Equation 3 is obtained by substituting the Equation 1 field-capacity tension into + * Equation 2. + */ +[[nodiscard]] +inline double calc_ptf_oosterveld1980_field_capacity(double clay, double sand, double mean_depth) { + return (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * std::pow(clay, -0.080); +} + +/** + * @brief Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + * @param clay Clay content in percent by weight. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @return Predicted soil-moisture content at 1500 kPa in percent by weight. (% mass) + * + * @details Prediction target: + * Gravimetric soil-moisture content at 1500 kPa. + * @note Equation 4 reports r = 0.96 and n = 298. + * @note The paper takes moisture content at 1500 kPa as the wilting point. + */ +[[nodiscard]] +inline double calc_ptf_oosterveld1980_wilting_point(double clay, double sand, double mean_depth) { + return 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth; +} + +/** + * @brief Estimate available gravimetric soil moisture between field capacity and wilting + * point. + * @param clay Clay content in percent by weight; the field-capacity tension experiment covered + * 2% to 72% clay. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @return Predicted field-capacity moisture minus predicted wilting-point moisture, in + * percentage points by weight. (% mass) + * + * @details Prediction target: + * Difference between gravimetric moisture content at field capacity and at the 1500 kPa + * wilting point. + * @note The paper defines available soil water by subtracting Equation 4 from Equation 3. + */ +[[nodiscard]] +inline double calc_ptf_oosterveld1980_available_water(double clay, double sand, double mean_depth) { + const double field_capacity_moisture = + (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * std::pow(clay, -0.080); + const double wilting_point_moisture = 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth; + return field_capacity_moisture - wilting_point_moisture; +} + +} // namespace ptfkit::oosterveld1980 diff --git a/targets/ptfkit-native/cpp/ptfkit.cppm b/targets/ptfkit-native/cpp/ptfkit.cppm index 5f60680..02fff9a 100644 --- a/targets/ptfkit-native/cpp/ptfkit.cppm +++ b/targets/ptfkit-native/cpp/ptfkit.cppm @@ -13,9 +13,11 @@ export import ptfkit.hodnett2002; export import ptfkit.jabro1992; export import ptfkit.li2007; export import ptfkit.mayr1999; +export import ptfkit.oosterveld1980; export import ptfkit.pidgeon1972; export import ptfkit.puckett1985; export import ptfkit.rawls1982; +export import ptfkit.saxton2006; export import ptfkit.tiwary2014; export import ptfkit.varallyai1982; export import ptfkit.vereecken1989; diff --git a/targets/ptfkit-native/cpp/saxton2006.cppm b/targets/ptfkit-native/cpp/saxton2006.cppm new file mode 100644 index 0000000..e5b4e9c --- /dev/null +++ b/targets/ptfkit-native/cpp/saxton2006.cppm @@ -0,0 +1,341 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +module; +#include + +export module ptfkit.saxton2006; + +/** + * @brief Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + * + * @details Source publication: + * Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic estimates by texture and + * organic matter for hydrologic solutions. Soil Science Society of America Journal, 70(5), + * 1569-1578. + * @see https://doi.org/10.2136/ssaj2005.0117 DOI: 10.2136/ssaj2005.0117 + * + * @remark Geographic scope: + * United States + * + * @remark Calibration dataset: + * 1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National Soil + * Characterization database after exclusions from 2,149 samples. + */ + +export namespace ptfkit::saxton2006 { + +struct Saxton2006PTFResult { + /** + * @brief Volumetric water content at 1500 kPa matric tension. (m^3/m^3) + */ + double theta_1500; + /** + * @brief Volumetric water content at 33 kPa matric tension. (m^3/m^3) + */ + double theta_33; + /** + * @brief Saturated volumetric water content at normal density. (m^3/m^3) + */ + double theta_s; + /** + * @brief Difference between the 33 and 1500 kPa water contents. (m^3/m^3) + */ + double plant_available_water; + /** + * @brief Air-entry or bubbling-pressure tension. (kPa) + */ + double air_entry_tension; + /** + * @brief Coefficient A of the dry-range moisture-tension curve. (kPa) + */ + double retention_a; + /** + * @brief Exponent B of the dry-range moisture-tension curve. (dimensionless) + */ + double retention_b; + /** + * @brief Inverse of retention exponent B. (dimensionless) + */ + double conductivity_lambda; + /** + * @brief Saturated hydraulic conductivity of the matric soil. (mm/h) + */ + double saturated_conductivity; + /** + * @brief Normal dry bulk density assuming particle density 2.65 g/cm^3. (g/cm^3) + */ + double normal_density; +}; + +struct Saxton2006DensityResult { + /** + * @brief Density after applying the density factor. (g/cm^3) + */ + double adjusted_density; + /** + * @brief Saturated water content after the density adjustment. (m^3/m^3) + */ + double adjusted_theta_s; + /** + * @brief Water content at 33 kPa after the density adjustment. (m^3/m^3) + */ + double adjusted_theta_33; + /** + * @brief Density-adjusted difference between saturation and 33 kPa. (m^3/m^3) + */ + double adjusted_theta_s_minus_33; +}; + +struct Saxton2006GravelResult { + /** + * @brief Gravel volume fraction of the bulk soil. (m^3/m^3) + */ + double gravel_volume_fraction; + /** + * @brief Dry bulk density of matric soil plus gravel. (g/cm^3) + */ + double bulk_density; + /** + * @brief Plant-available water on a bulk-soil volume basis. (m^3/m^3) + */ + double bulk_plant_available_water; + /** + * @brief Saturated conductivity after the gravel reduction. (mm/h) + */ + double bulk_saturated_conductivity; +}; + +struct Saxton2006SalinityResult { + /** + * @brief Osmotic potential at saturation. (kPa) + */ + double saturated_osmotic_potential; + /** + * @brief Osmotic potential at the supplied water content. (kPa) + */ + double osmotic_potential; +}; + +/** + * @brief Estimate soil water characteristics from sand, clay, and organic matter. + * @param sand Sand mass fraction of the fine-earth soil. (g/g) + * @param clay Clay mass fraction of the fine-earth soil. (g/g) + * @param organic_matter Organic matter content on a mass percentage basis. (% mass) + * @return A result with the following fields: + * - `theta_1500` — Volumetric water content at 1500 kPa matric tension. (m^3/m^3) + * - `theta_33` — Volumetric water content at 33 kPa matric tension. (m^3/m^3) + * - `theta_s` — Saturated volumetric water content at normal density. (m^3/m^3) + * - `plant_available_water` — Difference between the 33 and 1500 kPa water contents. + * (m^3/m^3) + * - `air_entry_tension` — Air-entry or bubbling-pressure tension. (kPa) + * - `retention_a` — Coefficient A of the dry-range moisture-tension curve. (kPa) + * - `retention_b` — Exponent B of the dry-range moisture-tension curve. (dimensionless) + * - `conductivity_lambda` — Inverse of retention exponent B. (dimensionless) + * - `saturated_conductivity` — Saturated hydraulic conductivity of the matric soil. (mm/h) + * - `normal_density` — Normal dry bulk density assuming particle density 2.65 g/cm^3. + * (g/cm^3) + * + * @details Prediction target: + * Water contents at 1500, 33, and 0 kPa, plant-available water, air-entry tension, retention + * coefficients, normal density, and saturated hydraulic conductivity. + * @note Sand and clay are decimal mass fractions, while organic matter is a percentage. + * @note The 1500 and 33 kPa values are also termed wilting point and field capacity. + * @warning Do not apply the regression above 8% organic matter or 60% clay. + * @warning Sand and clay fractions must describe one soil and therefore must sum to at most 1. + * @warning These statistical-average estimates should be calibrated to local measurements when + * available. + */ +[[nodiscard]] +inline Saxton2006PTFResult calc_ptf_saxton2006(double sand, double clay, double organic_matter) { + const double theta_1500_preliminary = + -0.024 * sand + 0.487 * clay + 0.006 * organic_matter + 0.005 * sand * organic_matter - + 0.013 * clay * organic_matter + 0.068 * sand * clay + 0.031; + const double theta_1500 = theta_1500_preliminary + 0.14 * theta_1500_preliminary - 0.02; + const double theta_33_preliminary = -0.251 * sand + 0.195 * clay + 0.011 * organic_matter + + 0.006 * sand * organic_matter - + 0.027 * clay * organic_matter + 0.452 * sand * clay + 0.299; + const double theta_33 = theta_33_preliminary + 1.283 * std::pow(theta_33_preliminary, 2.0) - + 0.374 * theta_33_preliminary - 0.015; + const double theta_s_minus_33_preliminary = + 0.278 * sand + 0.034 * clay + 0.022 * organic_matter - 0.018 * sand * organic_matter - + 0.027 * clay * organic_matter - 0.584 * sand * clay + 0.078; + const double theta_s_minus_33 = + theta_s_minus_33_preliminary + 0.636 * theta_s_minus_33_preliminary - 0.107; + const double theta_s = theta_33 + theta_s_minus_33 - 0.097 * sand + 0.043; + const double plant_available_water = theta_33 - theta_1500; + const double air_entry_preliminary = + -21.67 * sand - 27.93 * clay - 81.97 * theta_s_minus_33_preliminary + + 71.12 * sand * theta_s_minus_33_preliminary + 8.29 * clay * theta_s_minus_33_preliminary + + 14.05 * sand * clay + 27.16; + const double air_entry_tension = air_entry_preliminary + + 0.02 * std::pow(air_entry_preliminary, 2.0) - + 0.113 * air_entry_preliminary - 0.70; + const double retention_b = + (std::log(1500.0) - std::log(33.0)) / (std::log(theta_33) - std::log(theta_1500)); + const double retention_a = std::exp(std::log(33.0) + retention_b * std::log(theta_33)); + const double conductivity_lambda = 1.0 / retention_b; + const double saturated_conductivity = + 1930.0 * std::pow(theta_s - theta_33, 3.0 - conductivity_lambda); + const double normal_density = (1.0 - theta_s) * 2.65; + return Saxton2006PTFResult{theta_1500, + theta_33, + theta_s, + plant_available_water, + air_entry_tension, + retention_a, + retention_b, + conductivity_lambda, + saturated_conductivity, + normal_density}; +} + +/** + * @brief Adjust Saxton and Rawls water characteristics for soil density. + * @param normal_density Normal dry bulk density estimated by the base model. (g/cm^3) + * @param theta_s Saturated water content at normal density. (m^3/m^3) + * @param theta_33 Water content at 33 kPa and normal density. (m^3/m^3) + * @param density_factor Multiplicative adjustment to normal density. (dimensionless) + * @return A result with the following fields: + * - `adjusted_density` — Density after applying the density factor. (g/cm^3) + * - `adjusted_theta_s` — Saturated water content after the density adjustment. (m^3/m^3) + * - `adjusted_theta_33` — Water content at 33 kPa after the density adjustment. (m^3/m^3) + * - `adjusted_theta_s_minus_33` — Density-adjusted difference between saturation and 33 kPa. + * (m^3/m^3) + * + * @details Prediction target: + * Density-adjusted saturation and 33 kPa water contents. + * @note The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + * @warning The source recommends density factors only from 0.9 to 1.3. + */ +[[nodiscard]] +inline Saxton2006DensityResult calc_ptf_saxton2006_density(double normal_density, double theta_s, + double theta_33, double density_factor) { + const double adjusted_density = normal_density * density_factor; + const double adjusted_theta_s = 1.0 - adjusted_density / 2.65; + const double adjusted_theta_33 = theta_33 - 0.2 * (theta_s - adjusted_theta_s); + const double adjusted_theta_s_minus_33 = std::fmax(adjusted_theta_s - adjusted_theta_33, 0.005); + return Saxton2006DensityResult{adjusted_density, adjusted_theta_s, adjusted_theta_33, + adjusted_theta_s_minus_33}; +} + +/** + * @brief Estimate matric tension in the 1500 to 33 kPa segment. + * @param theta Volumetric water content. (m^3/m^3) + * @param theta_1500 Volumetric water content at 1500 kPa. (m^3/m^3) + * @param theta_33 Volumetric water content at 33 kPa. (m^3/m^3) + * @return Matric tension at the supplied water content. (kPa) + * + * @details Prediction target: + * Matric tension for water content between theta_1500 and theta_33. + * @warning Use only for the 1500 to 33 kPa segment defined by the source. + */ +[[nodiscard]] +inline double calc_ptf_saxton2006_tension_dry(double theta, double theta_1500, double theta_33) { + const double retention_b = + (std::log(1500.0) - std::log(33.0)) / (std::log(theta_33) - std::log(theta_1500)); + const double retention_a = std::exp(std::log(33.0) + retention_b * std::log(theta_33)); + return retention_a * std::pow(theta, -retention_b); +} + +/** + * @brief Estimate matric tension in the 33 kPa to air-entry segment. + * @param theta Volumetric water content. (m^3/m^3) + * @param theta_33 Volumetric water content at 33 kPa. (m^3/m^3) + * @param theta_s Saturated volumetric water content. (m^3/m^3) + * @param air_entry_tension Air-entry tension estimated by the base model. (kPa) + * @return Matric tension at the supplied water content. (kPa) + * + * @details Prediction target: + * Matric tension for water content between theta_33 and theta_s. + * @note At tensions below air entry, Equation 13 fixes water content at theta_s. + * @warning Use only for the 33 kPa to air-entry segment defined by the source. + */ +[[nodiscard]] +inline double calc_ptf_saxton2006_tension_wet(double theta, double theta_33, double theta_s, + double air_entry_tension) { + return 33.0 - (theta - theta_33) * (33.0 - air_entry_tension) / (theta_s - theta_33); +} + +/** + * @brief Estimate unsaturated hydraulic conductivity from water content. + * @param theta Volumetric water content. (m^3/m^3) + * @param theta_s Saturated volumetric water content. (m^3/m^3) + * @param saturated_conductivity Saturated hydraulic conductivity of the matric soil. (mm/h) + * @param conductivity_lambda Inverse of retention exponent B. (dimensionless) + * @return Unsaturated hydraulic conductivity at the supplied water content. (mm/h) + * + * @details Prediction target: + * Unsaturated hydraulic conductivity of the matric soil. + * @warning The equation does not include residual water content. + */ +[[nodiscard]] +inline double calc_ptf_saxton2006_conductivity(double theta, double theta_s, + double saturated_conductivity, + double conductivity_lambda) { + return saturated_conductivity * std::pow(theta / theta_s, 3.0 + 2.0 / conductivity_lambda); +} + +/** + * @brief Adjust matric-soil properties for gravel content. + * @param gravel_weight_fraction Gravel mass fraction of the bulk soil. (g/g) + * @param matric_density Dry bulk density of the fine-earth matric soil. (g/cm^3) + * @param plant_available_water Plant-available water of the matric soil. (m^3/m^3) + * @param saturated_conductivity Saturated hydraulic conductivity of the matric soil. (mm/h) + * @return A result with the following fields: + * - `gravel_volume_fraction` — Gravel volume fraction of the bulk soil. (m^3/m^3) + * - `bulk_density` — Dry bulk density of matric soil plus gravel. (g/cm^3) + * - `bulk_plant_available_water` — Plant-available water on a bulk-soil volume basis. + * (m^3/m^3) + * - `bulk_saturated_conductivity` — Saturated conductivity after the gravel reduction. + * (mm/h) + * + * @details Prediction target: + * Gravel volume, bulk density, available water, and saturated conductivity. + * @note Gravel comprises particles larger than 2 mm. + * @note Gravel density is fixed at 2.65 g/cm^3 in the source equations. + * @warning The conductivity correction does not represent extra macropores sometimes found in + * gravelly soils. + */ +[[nodiscard]] +inline Saxton2006GravelResult +calc_ptf_saxton2006_gravel(double gravel_weight_fraction, double matric_density, + double plant_available_water, double saturated_conductivity) { + const double density_ratio = matric_density / 2.65; + const double gravel_volume_fraction = density_ratio * gravel_weight_fraction / + (1.0 - gravel_weight_fraction * (1.0 - density_ratio)); + const double bulk_density = + matric_density * (1.0 - gravel_volume_fraction) + gravel_volume_fraction * 2.65; + const double bulk_plant_available_water = + plant_available_water * (1.0 - gravel_volume_fraction); + const double bulk_saturated_conductivity = + saturated_conductivity * (1.0 - gravel_volume_fraction) / + (1.0 - gravel_volume_fraction * (1.0 - 3.0 * density_ratio / 2.0)); + return Saxton2006GravelResult{gravel_volume_fraction, bulk_density, bulk_plant_available_water, + bulk_saturated_conductivity}; +} + +/** + * @brief Estimate saturated and moisture-adjusted osmotic potential. + * @param electrical_conductivity Electrical conductivity of a saturated soil extract. (dS/m) + * @param theta Current volumetric water content. (m^3/m^3) + * @param theta_s Saturated volumetric water content. (m^3/m^3) + * @return A result with the following fields: + * - `saturated_osmotic_potential` — Osmotic potential at saturation. (kPa) + * - `osmotic_potential` — Osmotic potential at the supplied water content. (kPa) + * + * @details Prediction target: + * Osmotic potential from saturated-extract electrical conductivity. + * @note The moisture adjustment assumes dissolved salt quantity remains near constant as water + * content falls. + * @warning Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity + * effects. + */ +[[nodiscard]] +inline Saxton2006SalinityResult calc_ptf_saxton2006_salinity(double electrical_conductivity, + double theta, double theta_s) { + const double saturated_osmotic_potential = 36.0 * electrical_conductivity; + const double osmotic_potential = theta_s / theta * saturated_osmotic_potential; + return Saxton2006SalinityResult{saturated_osmotic_potential, osmotic_potential}; +} + +} // namespace ptfkit::saxton2006 diff --git a/targets/ptfkit-native/include/ptfkit/oosterveld1980.h b/targets/ptfkit-native/include/ptfkit/oosterveld1980.h new file mode 100644 index 0000000..3b9fe5d --- /dev/null +++ b/targets/ptfkit-native/include/ptfkit/oosterveld1980.h @@ -0,0 +1,114 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +#ifndef PTFKIT_OOSTERVELD1980_H +#define PTFKIT_OOSTERVELD1980_H + +#include + +/** + * @brief Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + * + * @details Source publication: + * Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory determinations of + * soil texture and moisture retention. Canadian Agricultural Engineering, 22, 149-151. + * + * @remark Geographic scope: + * Southern Alberta, Canada + * + * @remark Calibration dataset: + * 1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research Station + * laboratory; the field-capacity tension regression used 134 samples. + */ + +/** + * @brief Estimate field-capacity tension from clay content. + * @param clay Clay content in percent by weight; the field-capacity tension experiment covered + * 2% to 72% clay. (% mass) + * @return Predicted tension corresponding to cylinder-method field capacity. (kPa) + * + * @details Prediction target: + * Tension at which pressure-plate moisture content equals field capacity determined by the + * cylinder method. + * @note Equation 1 reports r = 0.67 and n = 134. + */ +static inline double calc_ptf_oosterveld1980_field_capacity_tension(double clay) { + return 5.356 * pow(clay, 0.421); +} + +/** + * @brief Estimate gravimetric soil-moisture content from texture, depth, and tension. + * @param clay Clay content in percent by weight. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @param tension Soil-moisture tension. (kPa) + * @return Predicted soil-moisture content in percent by weight. (% mass) + * + * @details Prediction target: + * Gravimetric soil-moisture content at a specified tension. + * @note Equation 2 reports r = 0.96 and n = 1,137. + * @warning The paper reports slight inaccuracy at very high clay content and high tension. + */ +static inline double calc_ptf_oosterveld1980_retention(double clay, double sand, double mean_depth, + double tension) { + return (35.367 + 0.644 * clay - 0.251 * sand - 0.045 * mean_depth) * pow(tension, -0.190); +} + +/** + * @brief Estimate gravimetric soil-moisture content at field capacity. + * @param clay Clay content in percent by weight; the field-capacity tension experiment covered + * 2% to 72% clay. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @return Predicted soil-moisture content at field capacity in percent by weight. (% mass) + * + * @details Prediction target: + * Gravimetric soil-moisture content at field capacity. + * @note Equation 3 is obtained by substituting the Equation 1 field-capacity tension into + * Equation 2. + */ +static inline double calc_ptf_oosterveld1980_field_capacity(double clay, double sand, + double mean_depth) { + return (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * pow(clay, -0.080); +} + +/** + * @brief Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + * @param clay Clay content in percent by weight. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @return Predicted soil-moisture content at 1500 kPa in percent by weight. (% mass) + * + * @details Prediction target: + * Gravimetric soil-moisture content at 1500 kPa. + * @note Equation 4 reports r = 0.96 and n = 298. + * @note The paper takes moisture content at 1500 kPa as the wilting point. + */ +static inline double calc_ptf_oosterveld1980_wilting_point(double clay, double sand, + double mean_depth) { + return 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth; +} + +/** + * @brief Estimate available gravimetric soil moisture between field capacity and wilting + * point. + * @param clay Clay content in percent by weight; the field-capacity tension experiment covered + * 2% to 72% clay. (% mass) + * @param sand Sand content in percent by weight. (% mass) + * @param mean_depth Mean depth of the soil sample. (cm) + * @return Predicted field-capacity moisture minus predicted wilting-point moisture, in + * percentage points by weight. (% mass) + * + * @details Prediction target: + * Difference between gravimetric moisture content at field capacity and at the 1500 kPa + * wilting point. + * @note The paper defines available soil water by subtracting Equation 4 from Equation 3. + */ +static inline double calc_ptf_oosterveld1980_available_water(double clay, double sand, + double mean_depth) { + const double field_capacity_moisture = + (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * pow(clay, -0.080); + const double wilting_point_moisture = 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth; + return field_capacity_moisture - wilting_point_moisture; +} + +#endif diff --git a/targets/ptfkit-native/include/ptfkit/ptfkit.h b/targets/ptfkit-native/include/ptfkit/ptfkit.h index 76bfbcd..cd74044 100644 --- a/targets/ptfkit-native/include/ptfkit/ptfkit.h +++ b/targets/ptfkit-native/include/ptfkit/ptfkit.h @@ -14,9 +14,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include diff --git a/targets/ptfkit-native/include/ptfkit/saxton2006.h b/targets/ptfkit-native/include/ptfkit/saxton2006.h new file mode 100644 index 0000000..e767cd9 --- /dev/null +++ b/targets/ptfkit-native/include/ptfkit/saxton2006.h @@ -0,0 +1,374 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +#ifndef PTFKIT_SAXTON2006_H +#define PTFKIT_SAXTON2006_H + +#include + +/** + * @brief Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + * + * @details Source publication: + * Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic estimates by texture and + * organic matter for hydrologic solutions. Soil Science Society of America Journal, 70(5), + * 1569-1578. + * @see https://doi.org/10.2136/ssaj2005.0117 DOI: 10.2136/ssaj2005.0117 + * + * @remark Geographic scope: + * United States + * + * @remark Calibration dataset: + * 1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National Soil + * Characterization database after exclusions from 2,149 samples. + */ + +typedef struct { + /** + * @brief Volumetric water content at 1500 kPa matric tension. (m^3/m^3) + */ + double theta_1500; + /** + * @brief Volumetric water content at 33 kPa matric tension. (m^3/m^3) + */ + double theta_33; + /** + * @brief Saturated volumetric water content at normal density. (m^3/m^3) + */ + double theta_s; + /** + * @brief Difference between the 33 and 1500 kPa water contents. (m^3/m^3) + */ + double plant_available_water; + /** + * @brief Air-entry or bubbling-pressure tension. (kPa) + */ + double air_entry_tension; + /** + * @brief Coefficient A of the dry-range moisture-tension curve. (kPa) + */ + double retention_a; + /** + * @brief Exponent B of the dry-range moisture-tension curve. (dimensionless) + */ + double retention_b; + /** + * @brief Inverse of retention exponent B. (dimensionless) + */ + double conductivity_lambda; + /** + * @brief Saturated hydraulic conductivity of the matric soil. (mm/h) + */ + double saturated_conductivity; + /** + * @brief Normal dry bulk density assuming particle density 2.65 g/cm^3. (g/cm^3) + */ + double normal_density; +} saxton2006_ptf_result; + +typedef struct { + /** + * @brief Density after applying the density factor. (g/cm^3) + */ + double adjusted_density; + /** + * @brief Saturated water content after the density adjustment. (m^3/m^3) + */ + double adjusted_theta_s; + /** + * @brief Water content at 33 kPa after the density adjustment. (m^3/m^3) + */ + double adjusted_theta_33; + /** + * @brief Density-adjusted difference between saturation and 33 kPa. (m^3/m^3) + */ + double adjusted_theta_s_minus_33; +} saxton2006_density_result; + +typedef struct { + /** + * @brief Gravel volume fraction of the bulk soil. (m^3/m^3) + */ + double gravel_volume_fraction; + /** + * @brief Dry bulk density of matric soil plus gravel. (g/cm^3) + */ + double bulk_density; + /** + * @brief Plant-available water on a bulk-soil volume basis. (m^3/m^3) + */ + double bulk_plant_available_water; + /** + * @brief Saturated conductivity after the gravel reduction. (mm/h) + */ + double bulk_saturated_conductivity; +} saxton2006_gravel_result; + +typedef struct { + /** + * @brief Osmotic potential at saturation. (kPa) + */ + double saturated_osmotic_potential; + /** + * @brief Osmotic potential at the supplied water content. (kPa) + */ + double osmotic_potential; +} saxton2006_salinity_result; + +/** + * @brief Estimate soil water characteristics from sand, clay, and organic matter. + * @param sand Sand mass fraction of the fine-earth soil. (g/g) + * @param clay Clay mass fraction of the fine-earth soil. (g/g) + * @param organic_matter Organic matter content on a mass percentage basis. (% mass) + * @return A result with the following fields: + * - `theta_1500` — Volumetric water content at 1500 kPa matric tension. (m^3/m^3) + * - `theta_33` — Volumetric water content at 33 kPa matric tension. (m^3/m^3) + * - `theta_s` — Saturated volumetric water content at normal density. (m^3/m^3) + * - `plant_available_water` — Difference between the 33 and 1500 kPa water contents. + * (m^3/m^3) + * - `air_entry_tension` — Air-entry or bubbling-pressure tension. (kPa) + * - `retention_a` — Coefficient A of the dry-range moisture-tension curve. (kPa) + * - `retention_b` — Exponent B of the dry-range moisture-tension curve. (dimensionless) + * - `conductivity_lambda` — Inverse of retention exponent B. (dimensionless) + * - `saturated_conductivity` — Saturated hydraulic conductivity of the matric soil. (mm/h) + * - `normal_density` — Normal dry bulk density assuming particle density 2.65 g/cm^3. + * (g/cm^3) + * + * @details Prediction target: + * Water contents at 1500, 33, and 0 kPa, plant-available water, air-entry tension, retention + * coefficients, normal density, and saturated hydraulic conductivity. + * @note Sand and clay are decimal mass fractions, while organic matter is a percentage. + * @note The 1500 and 33 kPa values are also termed wilting point and field capacity. + * @warning Do not apply the regression above 8% organic matter or 60% clay. + * @warning Sand and clay fractions must describe one soil and therefore must sum to at most 1. + * @warning These statistical-average estimates should be calibrated to local measurements when + * available. + */ +static inline saxton2006_ptf_result calc_ptf_saxton2006(double sand, double clay, + double organic_matter) { + const double theta_1500_preliminary = + -0.024 * sand + 0.487 * clay + 0.006 * organic_matter + 0.005 * sand * organic_matter - + 0.013 * clay * organic_matter + 0.068 * sand * clay + 0.031; + const double theta_1500 = theta_1500_preliminary + 0.14 * theta_1500_preliminary - 0.02; + const double theta_33_preliminary = -0.251 * sand + 0.195 * clay + 0.011 * organic_matter + + 0.006 * sand * organic_matter - + 0.027 * clay * organic_matter + 0.452 * sand * clay + 0.299; + const double theta_33 = theta_33_preliminary + 1.283 * pow(theta_33_preliminary, 2.0) - + 0.374 * theta_33_preliminary - 0.015; + const double theta_s_minus_33_preliminary = + 0.278 * sand + 0.034 * clay + 0.022 * organic_matter - 0.018 * sand * organic_matter - + 0.027 * clay * organic_matter - 0.584 * sand * clay + 0.078; + const double theta_s_minus_33 = + theta_s_minus_33_preliminary + 0.636 * theta_s_minus_33_preliminary - 0.107; + const double theta_s = theta_33 + theta_s_minus_33 - 0.097 * sand + 0.043; + const double plant_available_water = theta_33 - theta_1500; + const double air_entry_preliminary = + -21.67 * sand - 27.93 * clay - 81.97 * theta_s_minus_33_preliminary + + 71.12 * sand * theta_s_minus_33_preliminary + 8.29 * clay * theta_s_minus_33_preliminary + + 14.05 * sand * clay + 27.16; + const double air_entry_tension = air_entry_preliminary + + 0.02 * pow(air_entry_preliminary, 2.0) - + 0.113 * air_entry_preliminary - 0.70; + const double retention_b = (log(1500.0) - log(33.0)) / (log(theta_33) - log(theta_1500)); + const double retention_a = exp(log(33.0) + retention_b * log(theta_33)); + const double conductivity_lambda = 1.0 / retention_b; + const double saturated_conductivity = + 1930.0 * pow(theta_s - theta_33, 3.0 - conductivity_lambda); + const double normal_density = (1.0 - theta_s) * 2.65; +#ifdef __cplusplus + return saxton2006_ptf_result{theta_1500, + theta_33, + theta_s, + plant_available_water, + air_entry_tension, + retention_a, + retention_b, + conductivity_lambda, + saturated_conductivity, + normal_density}; +#else + return (saxton2006_ptf_result){ + .theta_1500 = theta_1500, + .theta_33 = theta_33, + .theta_s = theta_s, + .plant_available_water = plant_available_water, + .air_entry_tension = air_entry_tension, + .retention_a = retention_a, + .retention_b = retention_b, + .conductivity_lambda = conductivity_lambda, + .saturated_conductivity = saturated_conductivity, + .normal_density = normal_density, + }; +#endif +} + +/** + * @brief Adjust Saxton and Rawls water characteristics for soil density. + * @param normal_density Normal dry bulk density estimated by the base model. (g/cm^3) + * @param theta_s Saturated water content at normal density. (m^3/m^3) + * @param theta_33 Water content at 33 kPa and normal density. (m^3/m^3) + * @param density_factor Multiplicative adjustment to normal density. (dimensionless) + * @return A result with the following fields: + * - `adjusted_density` — Density after applying the density factor. (g/cm^3) + * - `adjusted_theta_s` — Saturated water content after the density adjustment. (m^3/m^3) + * - `adjusted_theta_33` — Water content at 33 kPa after the density adjustment. (m^3/m^3) + * - `adjusted_theta_s_minus_33` — Density-adjusted difference between saturation and 33 kPa. + * (m^3/m^3) + * + * @details Prediction target: + * Density-adjusted saturation and 33 kPa water contents. + * @note The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + * @warning The source recommends density factors only from 0.9 to 1.3. + */ +static inline saxton2006_density_result calc_ptf_saxton2006_density(double normal_density, + double theta_s, double theta_33, + double density_factor) { + const double adjusted_density = normal_density * density_factor; + const double adjusted_theta_s = 1.0 - adjusted_density / 2.65; + const double adjusted_theta_33 = theta_33 - 0.2 * (theta_s - adjusted_theta_s); + const double adjusted_theta_s_minus_33 = fmax(adjusted_theta_s - adjusted_theta_33, 0.005); +#ifdef __cplusplus + return saxton2006_density_result{adjusted_density, adjusted_theta_s, adjusted_theta_33, + adjusted_theta_s_minus_33}; +#else + return (saxton2006_density_result){ + .adjusted_density = adjusted_density, + .adjusted_theta_s = adjusted_theta_s, + .adjusted_theta_33 = adjusted_theta_33, + .adjusted_theta_s_minus_33 = adjusted_theta_s_minus_33, + }; +#endif +} + +/** + * @brief Estimate matric tension in the 1500 to 33 kPa segment. + * @param theta Volumetric water content. (m^3/m^3) + * @param theta_1500 Volumetric water content at 1500 kPa. (m^3/m^3) + * @param theta_33 Volumetric water content at 33 kPa. (m^3/m^3) + * @return Matric tension at the supplied water content. (kPa) + * + * @details Prediction target: + * Matric tension for water content between theta_1500 and theta_33. + * @warning Use only for the 1500 to 33 kPa segment defined by the source. + */ +static inline double calc_ptf_saxton2006_tension_dry(double theta, double theta_1500, + double theta_33) { + const double retention_b = (log(1500.0) - log(33.0)) / (log(theta_33) - log(theta_1500)); + const double retention_a = exp(log(33.0) + retention_b * log(theta_33)); + return retention_a * pow(theta, -retention_b); +} + +/** + * @brief Estimate matric tension in the 33 kPa to air-entry segment. + * @param theta Volumetric water content. (m^3/m^3) + * @param theta_33 Volumetric water content at 33 kPa. (m^3/m^3) + * @param theta_s Saturated volumetric water content. (m^3/m^3) + * @param air_entry_tension Air-entry tension estimated by the base model. (kPa) + * @return Matric tension at the supplied water content. (kPa) + * + * @details Prediction target: + * Matric tension for water content between theta_33 and theta_s. + * @note At tensions below air entry, Equation 13 fixes water content at theta_s. + * @warning Use only for the 33 kPa to air-entry segment defined by the source. + */ +static inline double calc_ptf_saxton2006_tension_wet(double theta, double theta_33, double theta_s, + double air_entry_tension) { + return 33.0 - (theta - theta_33) * (33.0 - air_entry_tension) / (theta_s - theta_33); +} + +/** + * @brief Estimate unsaturated hydraulic conductivity from water content. + * @param theta Volumetric water content. (m^3/m^3) + * @param theta_s Saturated volumetric water content. (m^3/m^3) + * @param saturated_conductivity Saturated hydraulic conductivity of the matric soil. (mm/h) + * @param conductivity_lambda Inverse of retention exponent B. (dimensionless) + * @return Unsaturated hydraulic conductivity at the supplied water content. (mm/h) + * + * @details Prediction target: + * Unsaturated hydraulic conductivity of the matric soil. + * @warning The equation does not include residual water content. + */ +static inline double calc_ptf_saxton2006_conductivity(double theta, double theta_s, + double saturated_conductivity, + double conductivity_lambda) { + return saturated_conductivity * pow(theta / theta_s, 3.0 + 2.0 / conductivity_lambda); +} + +/** + * @brief Adjust matric-soil properties for gravel content. + * @param gravel_weight_fraction Gravel mass fraction of the bulk soil. (g/g) + * @param matric_density Dry bulk density of the fine-earth matric soil. (g/cm^3) + * @param plant_available_water Plant-available water of the matric soil. (m^3/m^3) + * @param saturated_conductivity Saturated hydraulic conductivity of the matric soil. (mm/h) + * @return A result with the following fields: + * - `gravel_volume_fraction` — Gravel volume fraction of the bulk soil. (m^3/m^3) + * - `bulk_density` — Dry bulk density of matric soil plus gravel. (g/cm^3) + * - `bulk_plant_available_water` — Plant-available water on a bulk-soil volume basis. + * (m^3/m^3) + * - `bulk_saturated_conductivity` — Saturated conductivity after the gravel reduction. + * (mm/h) + * + * @details Prediction target: + * Gravel volume, bulk density, available water, and saturated conductivity. + * @note Gravel comprises particles larger than 2 mm. + * @note Gravel density is fixed at 2.65 g/cm^3 in the source equations. + * @warning The conductivity correction does not represent extra macropores sometimes found in + * gravelly soils. + */ +static inline saxton2006_gravel_result calc_ptf_saxton2006_gravel(double gravel_weight_fraction, + double matric_density, + double plant_available_water, + double saturated_conductivity) { + const double density_ratio = matric_density / 2.65; + const double gravel_volume_fraction = density_ratio * gravel_weight_fraction / + (1.0 - gravel_weight_fraction * (1.0 - density_ratio)); + const double bulk_density = + matric_density * (1.0 - gravel_volume_fraction) + gravel_volume_fraction * 2.65; + const double bulk_plant_available_water = + plant_available_water * (1.0 - gravel_volume_fraction); + const double bulk_saturated_conductivity = + saturated_conductivity * (1.0 - gravel_volume_fraction) / + (1.0 - gravel_volume_fraction * (1.0 - 3.0 * density_ratio / 2.0)); +#ifdef __cplusplus + return saxton2006_gravel_result{gravel_volume_fraction, bulk_density, + bulk_plant_available_water, bulk_saturated_conductivity}; +#else + return (saxton2006_gravel_result){ + .gravel_volume_fraction = gravel_volume_fraction, + .bulk_density = bulk_density, + .bulk_plant_available_water = bulk_plant_available_water, + .bulk_saturated_conductivity = bulk_saturated_conductivity, + }; +#endif +} + +/** + * @brief Estimate saturated and moisture-adjusted osmotic potential. + * @param electrical_conductivity Electrical conductivity of a saturated soil extract. (dS/m) + * @param theta Current volumetric water content. (m^3/m^3) + * @param theta_s Saturated volumetric water content. (m^3/m^3) + * @return A result with the following fields: + * - `saturated_osmotic_potential` — Osmotic potential at saturation. (kPa) + * - `osmotic_potential` — Osmotic potential at the supplied water content. (kPa) + * + * @details Prediction target: + * Osmotic potential from saturated-extract electrical conductivity. + * @note The moisture adjustment assumes dissolved salt quantity remains near constant as water + * content falls. + * @warning Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity + * effects. + */ +static inline saxton2006_salinity_result +calc_ptf_saxton2006_salinity(double electrical_conductivity, double theta, double theta_s) { + const double saturated_osmotic_potential = 36.0 * electrical_conductivity; + const double osmotic_potential = theta_s / theta * saturated_osmotic_potential; +#ifdef __cplusplus + return saxton2006_salinity_result{saturated_osmotic_potential, osmotic_potential}; +#else + return (saxton2006_salinity_result){ + .saturated_osmotic_potential = saturated_osmotic_potential, + .osmotic_potential = osmotic_potential, + }; +#endif +} + +#endif diff --git a/targets/ptfkit-native/tests/c/oosterveld1980.c b/targets/ptfkit-native/tests/c/oosterveld1980.c new file mode 100644 index 0000000..604a04b --- /dev/null +++ b/targets/ptfkit-native/tests/c/oosterveld1980.c @@ -0,0 +1,28 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +#include +#include "close_enough.h" + +int main() { + { + const double result = calc_ptf_oosterveld1980_field_capacity_tension(6.6); + assert_close_enough(result, 11.8540994164288, 0.000000000001, 0.000000000001); + } + { + const double result = calc_ptf_oosterveld1980_retention(6.6, 86.3, 105.0, 11.8); + assert_close_enough(result, 8.2782468100413, 0.000000000001, 0.000000000001); + } + { + const double result = calc_ptf_oosterveld1980_field_capacity(6.6, 86.3, 105.0); + assert_close_enough(result, 8.14707947394088, 0.000000000001, 0.000000000001); + } + { + const double result = calc_ptf_oosterveld1980_wilting_point(6.6, 86.3, 105.0); + assert_close_enough(result, 1.3942, 0.000000000001, 0.000000000001); + } + { + const double result = calc_ptf_oosterveld1980_available_water(6.6, 86.3, 105.0); + assert_close_enough(result, 6.75287947394088, 0.000000000001, 0.000000000001); + } + return 0; +} diff --git a/targets/ptfkit-native/tests/c/saxton2006.c b/targets/ptfkit-native/tests/c/saxton2006.c new file mode 100644 index 0000000..edf7c80 --- /dev/null +++ b/targets/ptfkit-native/tests/c/saxton2006.c @@ -0,0 +1,74 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +#include +#include "close_enough.h" + +int main() { + { + const saxton2006_ptf_result result = calc_ptf_saxton2006(0.88, 0.05, 2.5); + assert_close_enough(result.theta_1500, 0.05022058, 0.000000000001, 0.000000000001); + assert_close_enough(result.theta_33, 0.10282792364858702, 0.000000000001, 0.000000000001); + assert_close_enough(result.theta_s, 0.46172240764858724, 0.000000000001, 0.000000000001); + assert_close_enough(result.plant_available_water, 0.05260734364858702, 0.000000000001, + 0.000000000001); + assert_close_enough(result.air_entry_tension, 0.5986789729513293, 0.000000000001, + 0.000000000001); + assert_close_enough(result.retention_a, 0.00018076452552206025, 0.000000000001, + 0.000000000001); + assert_close_enough(result.retention_b, 5.325903100453899, 0.000000000001, 0.000000000001); + assert_close_enough(result.conductivity_lambda, 0.18776158355467926, 0.000000000001, + 0.000000000001); + assert_close_enough(result.saturated_conductivity, 108.1478278507403, 0.000000000001, + 0.000000000001); + assert_close_enough(result.normal_density, 1.4264356197312436, 0.000000000001, + 0.000000000001); + } + { + const saxton2006_density_result result = calc_ptf_saxton2006_density( + 1.4264356197312436, 0.46172240764858724, 0.10282792364858702, 1.1); + assert_close_enough(result.adjusted_density, 1.569079181704368, 0.000000000001, + 0.000000000001); + assert_close_enough(result.adjusted_theta_s, 0.407894648413446, 0.000000000001, + 0.000000000001); + assert_close_enough(result.adjusted_theta_33, 0.09206237180155877, 0.000000000001, + 0.000000000001); + assert_close_enough(result.adjusted_theta_s_minus_33, 0.3158322766118873, 0.000000000001, + 0.000000000001); + } + { + const double result = + calc_ptf_saxton2006_tension_dry(0.07652425182429351, 0.05022058, 0.10282792364858702); + assert_close_enough(result, 159.18094591362183, 0.000000000001, 0.000000000001); + } + { + const double result = calc_ptf_saxton2006_tension_wet( + 0.2, 0.10282792364858702, 0.46172240764858724, 0.5986789729513293); + assert_close_enough(result, 24.227216407352152, 0.000000000001, 0.000000000001); + } + { + const double result = calc_ptf_saxton2006_conductivity( + 0.3, 0.46172240764858724, 108.1478278507403, 0.18776158355467926); + assert_close_enough(result, 0.3003203142764693, 0.000000000001, 0.000000000001); + } + { + const saxton2006_gravel_result result = calc_ptf_saxton2006_gravel( + 0.2, 1.4264356197312436, 0.05260734364858702, 108.1478278507403); + assert_close_enough(result.gravel_volume_fraction, 0.11860834455314037, 0.000000000001, + 0.000000000001); + assert_close_enough(result.bulk_density, 1.5715605653291098, 0.000000000001, + 0.000000000001); + assert_close_enough(result.bulk_plant_available_water, 0.04636767370708995, 0.000000000001, + 0.000000000001); + assert_close_enough(result.bulk_saturated_conductivity, 97.54880510583706, 0.000000000001, + 0.000000000001); + } + { + const saxton2006_salinity_result result = + calc_ptf_saxton2006_salinity(4.0, 0.3, 0.46172240764858724); + assert_close_enough(result.saturated_osmotic_potential, 144.0, 0.000000000001, + 0.000000000001); + assert_close_enough(result.osmotic_potential, 221.6267556713219, 0.000000000001, + 0.000000000001); + } + return 0; +} diff --git a/targets/ptfkit-native/tests/cpp/oosterveld1980.cpp b/targets/ptfkit-native/tests/cpp/oosterveld1980.cpp new file mode 100644 index 0000000..b7856d7 --- /dev/null +++ b/targets/ptfkit-native/tests/cpp/oosterveld1980.cpp @@ -0,0 +1,38 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +#ifdef IMPORT_UMBRELLA +import ptfkit; +#else +import ptfkit.oosterveld1980; +#endif + +#include "close_enough.h" + +int main() { + { + const auto result = + ptfkit::oosterveld1980::calc_ptf_oosterveld1980_field_capacity_tension(6.6); + assert_close_enough(result, 11.8540994164288, 0.000000000001, 0.000000000001); + } + { + const auto result = + ptfkit::oosterveld1980::calc_ptf_oosterveld1980_retention(6.6, 86.3, 105.0, 11.8); + assert_close_enough(result, 8.2782468100413, 0.000000000001, 0.000000000001); + } + { + const auto result = + ptfkit::oosterveld1980::calc_ptf_oosterveld1980_field_capacity(6.6, 86.3, 105.0); + assert_close_enough(result, 8.14707947394088, 0.000000000001, 0.000000000001); + } + { + const auto result = + ptfkit::oosterveld1980::calc_ptf_oosterveld1980_wilting_point(6.6, 86.3, 105.0); + assert_close_enough(result, 1.3942, 0.000000000001, 0.000000000001); + } + { + const auto result = + ptfkit::oosterveld1980::calc_ptf_oosterveld1980_available_water(6.6, 86.3, 105.0); + assert_close_enough(result, 6.75287947394088, 0.000000000001, 0.000000000001); + } + return 0; +} diff --git a/targets/ptfkit-native/tests/cpp/saxton2006.cpp b/targets/ptfkit-native/tests/cpp/saxton2006.cpp new file mode 100644 index 0000000..2e48738 --- /dev/null +++ b/targets/ptfkit-native/tests/cpp/saxton2006.cpp @@ -0,0 +1,88 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ + +#ifdef IMPORT_UMBRELLA +import ptfkit; +#else +import ptfkit.saxton2006; +#endif + +#include "close_enough.h" +#include + +int main() { + { + const auto result = ptfkit::saxton2006::calc_ptf_saxton2006(0.88, 0.05, 2.5); + static_assert(std::is_same_v, + ptfkit::saxton2006::Saxton2006PTFResult>); + assert_close_enough(result.theta_1500, 0.05022058, 0.000000000001, 0.000000000001); + assert_close_enough(result.theta_33, 0.10282792364858702, 0.000000000001, 0.000000000001); + assert_close_enough(result.theta_s, 0.46172240764858724, 0.000000000001, 0.000000000001); + assert_close_enough(result.plant_available_water, 0.05260734364858702, 0.000000000001, + 0.000000000001); + assert_close_enough(result.air_entry_tension, 0.5986789729513293, 0.000000000001, + 0.000000000001); + assert_close_enough(result.retention_a, 0.00018076452552206025, 0.000000000001, + 0.000000000001); + assert_close_enough(result.retention_b, 5.325903100453899, 0.000000000001, 0.000000000001); + assert_close_enough(result.conductivity_lambda, 0.18776158355467926, 0.000000000001, + 0.000000000001); + assert_close_enough(result.saturated_conductivity, 108.1478278507403, 0.000000000001, + 0.000000000001); + assert_close_enough(result.normal_density, 1.4264356197312436, 0.000000000001, + 0.000000000001); + } + { + const auto result = ptfkit::saxton2006::calc_ptf_saxton2006_density( + 1.4264356197312436, 0.46172240764858724, 0.10282792364858702, 1.1); + static_assert(std::is_same_v, + ptfkit::saxton2006::Saxton2006DensityResult>); + assert_close_enough(result.adjusted_density, 1.569079181704368, 0.000000000001, + 0.000000000001); + assert_close_enough(result.adjusted_theta_s, 0.407894648413446, 0.000000000001, + 0.000000000001); + assert_close_enough(result.adjusted_theta_33, 0.09206237180155877, 0.000000000001, + 0.000000000001); + assert_close_enough(result.adjusted_theta_s_minus_33, 0.3158322766118873, 0.000000000001, + 0.000000000001); + } + { + const auto result = ptfkit::saxton2006::calc_ptf_saxton2006_tension_dry( + 0.07652425182429351, 0.05022058, 0.10282792364858702); + assert_close_enough(result, 159.18094591362183, 0.000000000001, 0.000000000001); + } + { + const auto result = ptfkit::saxton2006::calc_ptf_saxton2006_tension_wet( + 0.2, 0.10282792364858702, 0.46172240764858724, 0.5986789729513293); + assert_close_enough(result, 24.227216407352152, 0.000000000001, 0.000000000001); + } + { + const auto result = ptfkit::saxton2006::calc_ptf_saxton2006_conductivity( + 0.3, 0.46172240764858724, 108.1478278507403, 0.18776158355467926); + assert_close_enough(result, 0.3003203142764693, 0.000000000001, 0.000000000001); + } + { + const auto result = ptfkit::saxton2006::calc_ptf_saxton2006_gravel( + 0.2, 1.4264356197312436, 0.05260734364858702, 108.1478278507403); + static_assert(std::is_same_v, + ptfkit::saxton2006::Saxton2006GravelResult>); + assert_close_enough(result.gravel_volume_fraction, 0.11860834455314037, 0.000000000001, + 0.000000000001); + assert_close_enough(result.bulk_density, 1.5715605653291098, 0.000000000001, + 0.000000000001); + assert_close_enough(result.bulk_plant_available_water, 0.04636767370708995, 0.000000000001, + 0.000000000001); + assert_close_enough(result.bulk_saturated_conductivity, 97.54880510583706, 0.000000000001, + 0.000000000001); + } + { + const auto result = + ptfkit::saxton2006::calc_ptf_saxton2006_salinity(4.0, 0.3, 0.46172240764858724); + static_assert(std::is_same_v, + ptfkit::saxton2006::Saxton2006SalinityResult>); + assert_close_enough(result.saturated_osmotic_potential, 144.0, 0.000000000001, + 0.000000000001); + assert_close_enough(result.osmotic_potential, 221.6267556713219, 0.000000000001, + 0.000000000001); + } + return 0; +} diff --git a/targets/ptfkit-py/src/ptfkit/_ptfkit.pyi b/targets/ptfkit-py/src/ptfkit/_ptfkit.pyi index 5da221d..d73443e 100644 --- a/targets/ptfkit-py/src/ptfkit/_ptfkit.pyi +++ b/targets/ptfkit-py/src/ptfkit/_ptfkit.pyi @@ -72,6 +72,11 @@ calc_ptf_hodnett2002: ufunc calc_ptf_jabro1992: ufunc calc_ptf_li2007: ufunc calc_ptf_mayr1999: ufunc +calc_ptf_oosterveld1980_available_water: ufunc +calc_ptf_oosterveld1980_field_capacity: ufunc +calc_ptf_oosterveld1980_field_capacity_tension: ufunc +calc_ptf_oosterveld1980_retention: ufunc +calc_ptf_oosterveld1980_wilting_point: ufunc calc_ptf_pidgeon1972_awc: ufunc calc_ptf_pidgeon1972_awc_coarse_sand: ufunc calc_ptf_pidgeon1972_awc_fine_sand: ufunc @@ -93,6 +98,13 @@ calc_ptf_puckett1985: ufunc calc_ptf_rawls1982_full_wrc: ufunc calc_ptf_rawls1982_theta_1500: ufunc calc_ptf_rawls1982_theta_33: ufunc +calc_ptf_saxton2006: ufunc +calc_ptf_saxton2006_conductivity: ufunc +calc_ptf_saxton2006_density: ufunc +calc_ptf_saxton2006_gravel: ufunc +calc_ptf_saxton2006_salinity: ufunc +calc_ptf_saxton2006_tension_dry: ufunc +calc_ptf_saxton2006_tension_wet: ufunc calc_ptf_tiwary2014_bsr: ufunc calc_ptf_tiwary2014_igp: ufunc calc_ptf_varallyai1982_chernozem_a: ufunc diff --git a/targets/ptfkit-py/src/ptfkit/oosterveld1980.c b/targets/ptfkit-py/src/ptfkit/oosterveld1980.c new file mode 100644 index 0000000..68be100 --- /dev/null +++ b/targets/ptfkit-py/src/ptfkit/oosterveld1980.c @@ -0,0 +1,122 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ +#include "ufunc.h" + +static void calc_ptf_oosterveld1980_field_capacity_tension_loop(char **args, + const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double clay = *(const double *)args[0]; + const double field_capacity_tension = 5.356 * pow(clay, 0.421); + *(double *)args[1] = field_capacity_tension; + for (int arg = 0; arg < 2; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_oosterveld1980_field_capacity_tension_functions[] = { + calc_ptf_oosterveld1980_field_capacity_tension_loop}; +static char calc_ptf_oosterveld1980_field_capacity_tension_types[] = {NPY_DOUBLE, NPY_DOUBLE}; + +static void calc_ptf_oosterveld1980_retention_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double clay = *(const double *)args[0]; + const double sand = *(const double *)args[1]; + const double mean_depth = *(const double *)args[2]; + const double tension = *(const double *)args[3]; + const double moisture_content = + (35.367 + 0.644 * clay - 0.251 * sand - 0.045 * mean_depth) * pow(tension, -0.190); + *(double *)args[4] = moisture_content; + for (int arg = 0; arg < 5; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_oosterveld1980_retention_functions[] = { + calc_ptf_oosterveld1980_retention_loop}; +static char calc_ptf_oosterveld1980_retention_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE, NPY_DOUBLE}; + +static void calc_ptf_oosterveld1980_field_capacity_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double clay = *(const double *)args[0]; + const double sand = *(const double *)args[1]; + const double mean_depth = *(const double *)args[2]; + const double field_capacity_moisture = + (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * pow(clay, -0.080); + *(double *)args[3] = field_capacity_moisture; + for (int arg = 0; arg < 4; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_oosterveld1980_field_capacity_functions[] = { + calc_ptf_oosterveld1980_field_capacity_loop}; +static char calc_ptf_oosterveld1980_field_capacity_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE}; + +static void calc_ptf_oosterveld1980_wilting_point_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double clay = *(const double *)args[0]; + const double sand = *(const double *)args[1]; + const double mean_depth = *(const double *)args[2]; + const double wilting_point_moisture = + 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth; + *(double *)args[3] = wilting_point_moisture; + for (int arg = 0; arg < 4; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_oosterveld1980_wilting_point_functions[] = { + calc_ptf_oosterveld1980_wilting_point_loop}; +static char calc_ptf_oosterveld1980_wilting_point_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE}; + +static void calc_ptf_oosterveld1980_available_water_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double clay = *(const double *)args[0]; + const double sand = *(const double *)args[1]; + const double mean_depth = *(const double *)args[2]; + const double field_capacity_moisture = + (25.713 + 0.469 * clay - 0.184 * sand - 0.0329 * mean_depth) * pow(clay, -0.080); + const double wilting_point_moisture = + 4.035 + 0.299 * clay - 0.034 * sand - 0.016 * mean_depth; + const double available_moisture = field_capacity_moisture - wilting_point_moisture; + *(double *)args[3] = available_moisture; + for (int arg = 0; arg < 4; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_oosterveld1980_available_water_functions[] = { + calc_ptf_oosterveld1980_available_water_loop}; +static char calc_ptf_oosterveld1980_available_water_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE}; + +int ptfkit_register_oosterveld1980(PyObject *module) { + if (ptfkit_add_ufunc(module, "calc_ptf_oosterveld1980_field_capacity_tension", + calc_ptf_oosterveld1980_field_capacity_tension_functions, + calc_ptf_oosterveld1980_field_capacity_tension_types, 1, 1) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_oosterveld1980_retention", + calc_ptf_oosterveld1980_retention_functions, + calc_ptf_oosterveld1980_retention_types, 4, 1) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_oosterveld1980_field_capacity", + calc_ptf_oosterveld1980_field_capacity_functions, + calc_ptf_oosterveld1980_field_capacity_types, 3, 1) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_oosterveld1980_wilting_point", + calc_ptf_oosterveld1980_wilting_point_functions, + calc_ptf_oosterveld1980_wilting_point_types, 3, 1) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_oosterveld1980_available_water", + calc_ptf_oosterveld1980_available_water_functions, + calc_ptf_oosterveld1980_available_water_types, 3, 1) < 0) + return -1; + return 0; +} diff --git a/targets/ptfkit-py/src/ptfkit/oosterveld1980.py b/targets/ptfkit-py/src/ptfkit/oosterveld1980.py new file mode 100644 index 0000000..cf75ca1 --- /dev/null +++ b/targets/ptfkit-py/src/ptfkit/oosterveld1980.py @@ -0,0 +1,304 @@ +# @generated by ptfkit-codegen; DO NOT EDIT. + +# ruff: noqa: E501, I001 + +r"""Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + +Reference: + Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory determinations of + soil texture and moisture retention. Canadian Agricultural Engineering, 22, 149-151. + +Territory + +: Southern Alberta, Canada + +Dataset + +: 1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research Station + laboratory; the field-capacity tension regression used 134 samples. + +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, overload + +from ptfkit._ptfkit import ( + calc_ptf_oosterveld1980_field_capacity_tension as _calc_ptf_oosterveld1980_field_capacity_tension, + calc_ptf_oosterveld1980_retention as _calc_ptf_oosterveld1980_retention, + calc_ptf_oosterveld1980_field_capacity as _calc_ptf_oosterveld1980_field_capacity, + calc_ptf_oosterveld1980_wilting_point as _calc_ptf_oosterveld1980_wilting_point, + calc_ptf_oosterveld1980_available_water as _calc_ptf_oosterveld1980_available_water, +) + + +if TYPE_CHECKING: + from numpy import floating + from numpy.typing import ArrayLike, NDArray + +__all__ = [ + 'calc_ptf_oosterveld1980_available_water', + 'calc_ptf_oosterveld1980_field_capacity', + 'calc_ptf_oosterveld1980_field_capacity_tension', + 'calc_ptf_oosterveld1980_retention', + 'calc_ptf_oosterveld1980_wilting_point', +] + + +@overload +def calc_ptf_oosterveld1980_field_capacity_tension(*, clay: float) -> floating: ... + + +@overload +def calc_ptf_oosterveld1980_field_capacity_tension( + *, + clay: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_ptf_oosterveld1980_field_capacity_tension( + *, + clay: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + r"""Estimate field-capacity tension from clay content. + + Arguments: + clay: Clay content in percent by weight; the field-capacity tension experiment covered 2% to + 72% clay. (% mass) + out: Optional output arrays for in-place calculation. + + Returns: + field_capacity_tension: Predicted tension corresponding to cylinder-method field capacity. + (kPa) + + Models: + $h(\theta)$: Field-capacity tension point estimate + + Notes: + Prediction target: Tension at which pressure-plate moisture content equals field capacity + determined by the cylinder method. + Equation 1 reports r = 0.67 and n = 134. + + """ + if out is None: + values = _calc_ptf_oosterveld1980_field_capacity_tension(clay) + else: + values = _calc_ptf_oosterveld1980_field_capacity_tension(clay, out=out) + + return values + + +@overload +def calc_ptf_oosterveld1980_retention( + *, clay: float, sand: float, mean_depth: float, tension: float +) -> floating: ... + + +@overload +def calc_ptf_oosterveld1980_retention( + *, + clay: ArrayLike, + sand: ArrayLike, + mean_depth: ArrayLike, + tension: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_ptf_oosterveld1980_retention( + *, + clay: float | ArrayLike, + sand: float | ArrayLike, + mean_depth: float | ArrayLike, + tension: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + r"""Estimate gravimetric soil-moisture content from texture, depth, and tension. + + Arguments: + clay: Clay content in percent by weight. (% mass) + sand: Sand content in percent by weight. (% mass) + mean_depth: Mean depth of the soil sample. (cm) + tension: Soil-moisture tension. (kPa) + out: Optional output arrays for in-place calculation. + + Returns: + moisture_content: Predicted soil-moisture content in percent by weight. (% mass) + + Models: + $h(\theta)$: Empirical power-law moisture-retention relation + + Notes: + Prediction target: Gravimetric soil-moisture content at a specified tension. + Equation 2 reports r = 0.96 and n = 1,137. + + Warning: + The paper reports slight inaccuracy at very high clay content and high tension. + + """ + if out is None: + values = _calc_ptf_oosterveld1980_retention(clay, sand, mean_depth, tension) + else: + values = _calc_ptf_oosterveld1980_retention(clay, sand, mean_depth, tension, out=out) + + return values + + +@overload +def calc_ptf_oosterveld1980_field_capacity( + *, clay: float, sand: float, mean_depth: float +) -> floating: ... + + +@overload +def calc_ptf_oosterveld1980_field_capacity( + *, + clay: ArrayLike, + sand: ArrayLike, + mean_depth: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_ptf_oosterveld1980_field_capacity( + *, + clay: float | ArrayLike, + sand: float | ArrayLike, + mean_depth: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + r"""Estimate gravimetric soil-moisture content at field capacity. + + Arguments: + clay: Clay content in percent by weight; the field-capacity tension experiment covered 2% to + 72% clay. (% mass) + sand: Sand content in percent by weight. (% mass) + mean_depth: Mean depth of the soil sample. (cm) + out: Optional output arrays for in-place calculation. + + Returns: + field_capacity_moisture: Predicted soil-moisture content at field capacity in percent by + weight. (% mass) + + Models: + $h(\theta)$: Field-capacity point estimate derived from Equations 1 and 2 + + Notes: + Prediction target: Gravimetric soil-moisture content at field capacity. + Equation 3 is obtained by substituting the Equation 1 field-capacity tension into Equation + 2. + + """ + if out is None: + values = _calc_ptf_oosterveld1980_field_capacity(clay, sand, mean_depth) + else: + values = _calc_ptf_oosterveld1980_field_capacity(clay, sand, mean_depth, out=out) + + return values + + +@overload +def calc_ptf_oosterveld1980_wilting_point( + *, clay: float, sand: float, mean_depth: float +) -> floating: ... + + +@overload +def calc_ptf_oosterveld1980_wilting_point( + *, + clay: ArrayLike, + sand: ArrayLike, + mean_depth: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_ptf_oosterveld1980_wilting_point( + *, + clay: float | ArrayLike, + sand: float | ArrayLike, + mean_depth: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + r"""Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + + Arguments: + clay: Clay content in percent by weight. (% mass) + sand: Sand content in percent by weight. (% mass) + mean_depth: Mean depth of the soil sample. (cm) + out: Optional output arrays for in-place calculation. + + Returns: + wilting_point_moisture: Predicted soil-moisture content at 1500 kPa in percent by weight. (% + mass) + + Models: + $h(\theta)$: Wilting-point estimate at 1500 kPa + + Notes: + Prediction target: Gravimetric soil-moisture content at 1500 kPa. + Equation 4 reports r = 0.96 and n = 298. + The paper takes moisture content at 1500 kPa as the wilting point. + + """ + if out is None: + values = _calc_ptf_oosterveld1980_wilting_point(clay, sand, mean_depth) + else: + values = _calc_ptf_oosterveld1980_wilting_point(clay, sand, mean_depth, out=out) + + return values + + +@overload +def calc_ptf_oosterveld1980_available_water( + *, clay: float, sand: float, mean_depth: float +) -> floating: ... + + +@overload +def calc_ptf_oosterveld1980_available_water( + *, + clay: ArrayLike, + sand: ArrayLike, + mean_depth: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_ptf_oosterveld1980_available_water( + *, + clay: float | ArrayLike, + sand: float | ArrayLike, + mean_depth: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + r"""Estimate available gravimetric soil moisture between field capacity and wilting point. + + Arguments: + clay: Clay content in percent by weight; the field-capacity tension experiment covered 2% to + 72% clay. (% mass) + sand: Sand content in percent by weight. (% mass) + mean_depth: Mean depth of the soil sample. (cm) + out: Optional output arrays for in-place calculation. + + Returns: + available_moisture: Predicted field-capacity moisture minus predicted wilting-point + moisture, in percentage points by weight. (% mass) + + Models: + $h(\theta)$: Available-moisture point estimate from Equations 3 and 4 + + Notes: + Prediction target: Difference between gravimetric moisture content at field capacity and at + the 1500 kPa wilting point. + The paper defines available soil water by subtracting Equation 4 from Equation 3. + + """ + if out is None: + values = _calc_ptf_oosterveld1980_available_water(clay, sand, mean_depth) + else: + values = _calc_ptf_oosterveld1980_available_water(clay, sand, mean_depth, out=out) + + return values diff --git a/targets/ptfkit-py/src/ptfkit/ptfkit.c b/targets/ptfkit-py/src/ptfkit/ptfkit.c index 35b4d97..69a0f94 100644 --- a/targets/ptfkit-py/src/ptfkit/ptfkit.c +++ b/targets/ptfkit-py/src/ptfkit/ptfkit.c @@ -16,9 +16,11 @@ #include "jabro1992.c" #include "li2007.c" #include "mayr1999.c" +#include "oosterveld1980.c" #include "pidgeon1972.c" #include "puckett1985.c" #include "rawls1982.c" +#include "saxton2006.c" #include "tiwary2014.c" #include "varallyai1982.c" #include "vereecken1989.c" @@ -77,6 +79,10 @@ PyMODINIT_FUNC PyInit__ptfkit(void) { Py_DECREF(module); return NULL; } + if (ptfkit_register_oosterveld1980(module) < 0) { + Py_DECREF(module); + return NULL; + } if (ptfkit_register_pidgeon1972(module) < 0) { Py_DECREF(module); return NULL; @@ -89,6 +95,10 @@ PyMODINIT_FUNC PyInit__ptfkit(void) { Py_DECREF(module); return NULL; } + if (ptfkit_register_saxton2006(module) < 0) { + Py_DECREF(module); + return NULL; + } if (ptfkit_register_tiwary2014(module) < 0) { Py_DECREF(module); return NULL; diff --git a/targets/ptfkit-py/src/ptfkit/saxton2006.c b/targets/ptfkit-py/src/ptfkit/saxton2006.c new file mode 100644 index 0000000..06ce898 --- /dev/null +++ b/targets/ptfkit-py/src/ptfkit/saxton2006.c @@ -0,0 +1,224 @@ +/* @generated by ptfkit-codegen; DO NOT EDIT. */ +#include "ufunc.h" + +static void calc_ptf_saxton2006_loop(char **args, const npy_intp *dimensions, const npy_intp *steps, + void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double sand = *(const double *)args[0]; + const double clay = *(const double *)args[1]; + const double organic_matter = *(const double *)args[2]; + const double theta_1500_preliminary = + -0.024 * sand + 0.487 * clay + 0.006 * organic_matter + 0.005 * sand * organic_matter - + 0.013 * clay * organic_matter + 0.068 * sand * clay + 0.031; + const double theta_1500 = theta_1500_preliminary + 0.14 * theta_1500_preliminary - 0.02; + const double theta_33_preliminary = + -0.251 * sand + 0.195 * clay + 0.011 * organic_matter + 0.006 * sand * organic_matter - + 0.027 * clay * organic_matter + 0.452 * sand * clay + 0.299; + const double theta_33 = theta_33_preliminary + 1.283 * pow(theta_33_preliminary, 2.0) - + 0.374 * theta_33_preliminary - 0.015; + const double theta_s_minus_33_preliminary = + 0.278 * sand + 0.034 * clay + 0.022 * organic_matter - 0.018 * sand * organic_matter - + 0.027 * clay * organic_matter - 0.584 * sand * clay + 0.078; + const double theta_s_minus_33 = + theta_s_minus_33_preliminary + 0.636 * theta_s_minus_33_preliminary - 0.107; + const double theta_s = theta_33 + theta_s_minus_33 - 0.097 * sand + 0.043; + const double plant_available_water = theta_33 - theta_1500; + const double air_entry_preliminary = + -21.67 * sand - 27.93 * clay - 81.97 * theta_s_minus_33_preliminary + + 71.12 * sand * theta_s_minus_33_preliminary + + 8.29 * clay * theta_s_minus_33_preliminary + 14.05 * sand * clay + 27.16; + const double air_entry_tension = air_entry_preliminary + + 0.02 * pow(air_entry_preliminary, 2.0) - + 0.113 * air_entry_preliminary - 0.70; + const double retention_b = (log(1500.0) - log(33.0)) / (log(theta_33) - log(theta_1500)); + const double retention_a = exp(log(33.0) + retention_b * log(theta_33)); + const double conductivity_lambda = 1.0 / retention_b; + const double saturated_conductivity = + 1930.0 * pow(theta_s - theta_33, 3.0 - conductivity_lambda); + const double normal_density = (1.0 - theta_s) * 2.65; + *(double *)args[3] = theta_1500; + *(double *)args[4] = theta_33; + *(double *)args[5] = theta_s; + *(double *)args[6] = plant_available_water; + *(double *)args[7] = air_entry_tension; + *(double *)args[8] = retention_a; + *(double *)args[9] = retention_b; + *(double *)args[10] = conductivity_lambda; + *(double *)args[11] = saturated_conductivity; + *(double *)args[12] = normal_density; + for (int arg = 0; arg < 13; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_saxton2006_functions[] = {calc_ptf_saxton2006_loop}; +static char calc_ptf_saxton2006_types[] = { + NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE}; + +static void calc_ptf_saxton2006_density_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double normal_density = *(const double *)args[0]; + const double theta_s = *(const double *)args[1]; + const double theta_33 = *(const double *)args[2]; + const double density_factor = *(const double *)args[3]; + const double adjusted_density = normal_density * density_factor; + const double adjusted_theta_s = 1.0 - adjusted_density / 2.65; + const double adjusted_theta_33 = theta_33 - 0.2 * (theta_s - adjusted_theta_s); + const double adjusted_theta_s_minus_33 = fmax(adjusted_theta_s - adjusted_theta_33, 0.005); + *(double *)args[4] = adjusted_density; + *(double *)args[5] = adjusted_theta_s; + *(double *)args[6] = adjusted_theta_33; + *(double *)args[7] = adjusted_theta_s_minus_33; + for (int arg = 0; arg < 8; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_saxton2006_density_functions[] = { + calc_ptf_saxton2006_density_loop}; +static char calc_ptf_saxton2006_density_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE}; + +static void calc_ptf_saxton2006_tension_dry_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double theta = *(const double *)args[0]; + const double theta_1500 = *(const double *)args[1]; + const double theta_33 = *(const double *)args[2]; + const double retention_b = (log(1500.0) - log(33.0)) / (log(theta_33) - log(theta_1500)); + const double retention_a = exp(log(33.0) + retention_b * log(theta_33)); + const double tension = retention_a * pow(theta, -retention_b); + *(double *)args[3] = tension; + for (int arg = 0; arg < 4; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_saxton2006_tension_dry_functions[] = { + calc_ptf_saxton2006_tension_dry_loop}; +static char calc_ptf_saxton2006_tension_dry_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE}; + +static void calc_ptf_saxton2006_tension_wet_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double theta = *(const double *)args[0]; + const double theta_33 = *(const double *)args[1]; + const double theta_s = *(const double *)args[2]; + const double air_entry_tension = *(const double *)args[3]; + const double tension = + 33.0 - (theta - theta_33) * (33.0 - air_entry_tension) / (theta_s - theta_33); + *(double *)args[4] = tension; + for (int arg = 0; arg < 5; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_saxton2006_tension_wet_functions[] = { + calc_ptf_saxton2006_tension_wet_loop}; +static char calc_ptf_saxton2006_tension_wet_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE, NPY_DOUBLE}; + +static void calc_ptf_saxton2006_conductivity_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double theta = *(const double *)args[0]; + const double theta_s = *(const double *)args[1]; + const double saturated_conductivity = *(const double *)args[2]; + const double conductivity_lambda = *(const double *)args[3]; + const double conductivity = + saturated_conductivity * pow(theta / theta_s, 3.0 + 2.0 / conductivity_lambda); + *(double *)args[4] = conductivity; + for (int arg = 0; arg < 5; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_saxton2006_conductivity_functions[] = { + calc_ptf_saxton2006_conductivity_loop}; +static char calc_ptf_saxton2006_conductivity_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE, NPY_DOUBLE}; + +static void calc_ptf_saxton2006_gravel_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double gravel_weight_fraction = *(const double *)args[0]; + const double matric_density = *(const double *)args[1]; + const double plant_available_water = *(const double *)args[2]; + const double saturated_conductivity = *(const double *)args[3]; + const double density_ratio = matric_density / 2.65; + const double gravel_volume_fraction = + density_ratio * gravel_weight_fraction / + (1.0 - gravel_weight_fraction * (1.0 - density_ratio)); + const double bulk_density = + matric_density * (1.0 - gravel_volume_fraction) + gravel_volume_fraction * 2.65; + const double bulk_plant_available_water = + plant_available_water * (1.0 - gravel_volume_fraction); + const double bulk_saturated_conductivity = + saturated_conductivity * (1.0 - gravel_volume_fraction) / + (1.0 - gravel_volume_fraction * (1.0 - 3.0 * density_ratio / 2.0)); + *(double *)args[4] = gravel_volume_fraction; + *(double *)args[5] = bulk_density; + *(double *)args[6] = bulk_plant_available_water; + *(double *)args[7] = bulk_saturated_conductivity; + for (int arg = 0; arg < 8; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_saxton2006_gravel_functions[] = { + calc_ptf_saxton2006_gravel_loop}; +static char calc_ptf_saxton2006_gravel_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE}; + +static void calc_ptf_saxton2006_salinity_loop(char **args, const npy_intp *dimensions, + const npy_intp *steps, void *data) { + npy_intp index; + for (index = 0; index < dimensions[0]; index++) { + const double electrical_conductivity = *(const double *)args[0]; + const double theta = *(const double *)args[1]; + const double theta_s = *(const double *)args[2]; + const double saturated_osmotic_potential = 36.0 * electrical_conductivity; + const double osmotic_potential = theta_s / theta * saturated_osmotic_potential; + *(double *)args[3] = saturated_osmotic_potential; + *(double *)args[4] = osmotic_potential; + for (int arg = 0; arg < 5; arg++) + args[arg] += steps[arg]; + } +} +static PyUFuncGenericFunction calc_ptf_saxton2006_salinity_functions[] = { + calc_ptf_saxton2006_salinity_loop}; +static char calc_ptf_saxton2006_salinity_types[] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, + NPY_DOUBLE}; + +int ptfkit_register_saxton2006(PyObject *module) { + if (ptfkit_add_ufunc(module, "calc_ptf_saxton2006", calc_ptf_saxton2006_functions, + calc_ptf_saxton2006_types, 3, 10) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_saxton2006_density", + calc_ptf_saxton2006_density_functions, calc_ptf_saxton2006_density_types, + 4, 4) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_saxton2006_tension_dry", + calc_ptf_saxton2006_tension_dry_functions, + calc_ptf_saxton2006_tension_dry_types, 3, 1) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_saxton2006_tension_wet", + calc_ptf_saxton2006_tension_wet_functions, + calc_ptf_saxton2006_tension_wet_types, 4, 1) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_saxton2006_conductivity", + calc_ptf_saxton2006_conductivity_functions, + calc_ptf_saxton2006_conductivity_types, 4, 1) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_saxton2006_gravel", calc_ptf_saxton2006_gravel_functions, + calc_ptf_saxton2006_gravel_types, 4, 4) < 0) + return -1; + if (ptfkit_add_ufunc(module, "calc_ptf_saxton2006_salinity", + calc_ptf_saxton2006_salinity_functions, calc_ptf_saxton2006_salinity_types, + 3, 2) < 0) + return -1; + return 0; +} diff --git a/targets/ptfkit-py/src/ptfkit/saxton2006.py b/targets/ptfkit-py/src/ptfkit/saxton2006.py new file mode 100644 index 0000000..5102ba0 --- /dev/null +++ b/targets/ptfkit-py/src/ptfkit/saxton2006.py @@ -0,0 +1,547 @@ +# @generated by ptfkit-codegen; DO NOT EDIT. + +# ruff: noqa: I001 + +r"""Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + +Reference: + Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic estimates by texture and organic + matter for hydrologic solutions. Soil Science Society of America Journal, 70(5), 1569-1578. + [DOI: 10.2136/ssaj2005.0117](https://doi.org/10.2136/ssaj2005.0117) + +Territory + +: United States + +Dataset + +: 1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National Soil Characterization + database after exclusions from 2,149 samples. + +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Generic, NamedTuple, TypeVar, overload + +from ptfkit._ptfkit import ( + calc_ptf_saxton2006 as _calc_ptf_saxton2006, + calc_ptf_saxton2006_density as _calc_ptf_saxton2006_density, + calc_ptf_saxton2006_tension_dry as _calc_ptf_saxton2006_tension_dry, + calc_ptf_saxton2006_tension_wet as _calc_ptf_saxton2006_tension_wet, + calc_ptf_saxton2006_conductivity as _calc_ptf_saxton2006_conductivity, + calc_ptf_saxton2006_gravel as _calc_ptf_saxton2006_gravel, + calc_ptf_saxton2006_salinity as _calc_ptf_saxton2006_salinity, +) + + +if TYPE_CHECKING: + from numpy import floating + from numpy.typing import ArrayLike, NDArray + +T = TypeVar('T') + + +class Saxton2006DensityResult(NamedTuple, Generic[T]): + """Results returned by the matching PTF. + + Attributes: + adjusted_density: Density after applying the density factor. (g/cm^3) + adjusted_theta_s: Saturated water content after the density adjustment. (m^3/m^3) + adjusted_theta_33: Water content at 33 kPa after the density adjustment. (m^3/m^3) + adjusted_theta_s_minus_33: Density-adjusted difference between saturation and 33 kPa. + (m^3/m^3) + + """ + + adjusted_density: T + adjusted_theta_s: T + adjusted_theta_33: T + adjusted_theta_s_minus_33: T + + +class Saxton2006GravelResult(NamedTuple, Generic[T]): + """Results returned by the matching PTF. + + Attributes: + gravel_volume_fraction: Gravel volume fraction of the bulk soil. (m^3/m^3) + bulk_density: Dry bulk density of matric soil plus gravel. (g/cm^3) + bulk_plant_available_water: Plant-available water on a bulk-soil volume basis. (m^3/m^3) + bulk_saturated_conductivity: Saturated conductivity after the gravel reduction. (mm/h) + + """ + + gravel_volume_fraction: T + bulk_density: T + bulk_plant_available_water: T + bulk_saturated_conductivity: T + + +class Saxton2006PTFResult(NamedTuple, Generic[T]): + """Results returned by the matching PTF. + + Attributes: + theta_1500: Volumetric water content at 1500 kPa matric tension. (m^3/m^3) + theta_33: Volumetric water content at 33 kPa matric tension. (m^3/m^3) + theta_s: Saturated volumetric water content at normal density. (m^3/m^3) + plant_available_water: Difference between the 33 and 1500 kPa water contents. (m^3/m^3) + air_entry_tension: Air-entry or bubbling-pressure tension. (kPa) + retention_a: Coefficient A of the dry-range moisture-tension curve. (kPa) + retention_b: Exponent B of the dry-range moisture-tension curve. (dimensionless) + conductivity_lambda: Inverse of retention exponent B. (dimensionless) + saturated_conductivity: Saturated hydraulic conductivity of the matric soil. (mm/h) + normal_density: Normal dry bulk density assuming particle density 2.65 g/cm^3. (g/cm^3) + + """ + + theta_1500: T + theta_33: T + theta_s: T + plant_available_water: T + air_entry_tension: T + retention_a: T + retention_b: T + conductivity_lambda: T + saturated_conductivity: T + normal_density: T + + +class Saxton2006SalinityResult(NamedTuple, Generic[T]): + """Results returned by the matching PTF. + + Attributes: + saturated_osmotic_potential: Osmotic potential at saturation. (kPa) + osmotic_potential: Osmotic potential at the supplied water content. (kPa) + + """ + + saturated_osmotic_potential: T + osmotic_potential: T + + +__all__ = [ + 'Saxton2006DensityResult', + 'Saxton2006GravelResult', + 'Saxton2006PTFResult', + 'Saxton2006SalinityResult', + 'calc_conductivity_saxton2006', + 'calc_density_adjustment_saxton2006', + 'calc_gravel_adjustment_saxton2006', + 'calc_osmotic_potential_saxton2006', + 'calc_ptf_saxton2006', + 'calc_tension_dry_saxton2006', + 'calc_tension_wet_saxton2006', +] + + +@overload +def calc_ptf_saxton2006( + *, sand: float, clay: float, organic_matter: float +) -> Saxton2006PTFResult[floating]: ... + + +@overload +def calc_ptf_saxton2006( + *, + sand: ArrayLike, + clay: ArrayLike, + organic_matter: ArrayLike, + out: Saxton2006PTFResult[NDArray[floating]] | None = None, +) -> Saxton2006PTFResult[NDArray[floating]]: ... + + +def calc_ptf_saxton2006( + *, + sand: float | ArrayLike, + clay: float | ArrayLike, + organic_matter: float | ArrayLike, + out: Saxton2006PTFResult[NDArray[floating]] | None = None, +) -> Saxton2006PTFResult[floating] | Saxton2006PTFResult[NDArray[floating]]: + r"""Estimate soil water characteristics from sand, clay, and organic matter. + + Arguments: + sand: Sand mass fraction of the fine-earth soil. (g/g) + clay: Clay mass fraction of the fine-earth soil. (g/g) + organic_matter: Organic matter content on a mass percentage basis. (% mass) + out: Optional output arrays for in-place calculation. + + Returns: + Saxton2006PTFResult: Results grouped by result attributes. + + Models: + $h(\theta)$: Campbell power function with a linear wet-range segment + $k(h)$: Campbell unsaturated conductivity model + + Notes: + Prediction target: Water contents at 1500, 33, and 0 kPa, plant-available water, air-entry + tension, retention coefficients, normal density, and saturated hydraulic conductivity. + Sand and clay are decimal mass fractions, while organic matter is a percentage. + The 1500 and 33 kPa values are also termed wilting point and field capacity. + + Warning: + Do not apply the regression above 8% organic matter or 60% clay. + Sand and clay fractions must describe one soil and therefore must sum to at most 1. + These statistical-average estimates should be calibrated to local measurements when + available. + + """ + if out is None: + values = _calc_ptf_saxton2006(sand, clay, organic_matter) + else: + values = _calc_ptf_saxton2006(sand, clay, organic_matter, out=tuple(out)) + + return Saxton2006PTFResult(*values) + + +@overload +def calc_density_adjustment_saxton2006( + *, normal_density: float, theta_s: float, theta_33: float, density_factor: float +) -> Saxton2006DensityResult[floating]: ... + + +@overload +def calc_density_adjustment_saxton2006( + *, + normal_density: ArrayLike, + theta_s: ArrayLike, + theta_33: ArrayLike, + density_factor: ArrayLike, + out: Saxton2006DensityResult[NDArray[floating]] | None = None, +) -> Saxton2006DensityResult[NDArray[floating]]: ... + + +def calc_density_adjustment_saxton2006( + *, + normal_density: float | ArrayLike, + theta_s: float | ArrayLike, + theta_33: float | ArrayLike, + density_factor: float | ArrayLike, + out: Saxton2006DensityResult[NDArray[floating]] | None = None, +) -> Saxton2006DensityResult[floating] | Saxton2006DensityResult[NDArray[floating]]: + r"""Adjust Saxton and Rawls water characteristics for soil density. + + Arguments: + normal_density: Normal dry bulk density estimated by the base model. (g/cm^3) + theta_s: Saturated water content at normal density. (m^3/m^3) + theta_33: Water content at 33 kPa and normal density. (m^3/m^3) + density_factor: Multiplicative adjustment to normal density. (dimensionless) + out: Optional output arrays for in-place calculation. + + Returns: + Saxton2006DensityResult: Results grouped by result attributes. + + Models: + $h(\theta)$: Density adjustment to the point estimates + + Notes: + Prediction target: Density-adjusted saturation and 33 kPa water contents. + The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + + Warning: + The source recommends density factors only from 0.9 to 1.3. + + """ + if out is None: + values = _calc_ptf_saxton2006_density(normal_density, theta_s, theta_33, density_factor) + else: + values = _calc_ptf_saxton2006_density( + normal_density, theta_s, theta_33, density_factor, out=tuple(out) + ) + + return Saxton2006DensityResult(*values) + + +@overload +def calc_tension_dry_saxton2006( + *, theta: float, theta_1500: float, theta_33: float +) -> floating: ... + + +@overload +def calc_tension_dry_saxton2006( + *, + theta: ArrayLike, + theta_1500: ArrayLike, + theta_33: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_tension_dry_saxton2006( + *, + theta: float | ArrayLike, + theta_1500: float | ArrayLike, + theta_33: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + r"""Estimate matric tension in the 1500 to 33 kPa segment. + + Arguments: + theta: Volumetric water content. (m^3/m^3) + theta_1500: Volumetric water content at 1500 kPa. (m^3/m^3) + theta_33: Volumetric water content at 33 kPa. (m^3/m^3) + out: Optional output arrays for in-place calculation. + + Returns: + tension: Matric tension at the supplied water content. (kPa) + + Models: + $h(\theta)$: Campbell power function + + Notes: + Prediction target: Matric tension for water content between theta_1500 and theta_33. + + Warning: + Use only for the 1500 to 33 kPa segment defined by the source. + + """ + if out is None: + values = _calc_ptf_saxton2006_tension_dry(theta, theta_1500, theta_33) + else: + values = _calc_ptf_saxton2006_tension_dry(theta, theta_1500, theta_33, out=out) + + return values + + +@overload +def calc_tension_wet_saxton2006( + *, theta: float, theta_33: float, theta_s: float, air_entry_tension: float +) -> floating: ... + + +@overload +def calc_tension_wet_saxton2006( + *, + theta: ArrayLike, + theta_33: ArrayLike, + theta_s: ArrayLike, + air_entry_tension: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_tension_wet_saxton2006( + *, + theta: float | ArrayLike, + theta_33: float | ArrayLike, + theta_s: float | ArrayLike, + air_entry_tension: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + r"""Estimate matric tension in the 33 kPa to air-entry segment. + + Arguments: + theta: Volumetric water content. (m^3/m^3) + theta_33: Volumetric water content at 33 kPa. (m^3/m^3) + theta_s: Saturated volumetric water content. (m^3/m^3) + air_entry_tension: Air-entry tension estimated by the base model. (kPa) + out: Optional output arrays for in-place calculation. + + Returns: + tension: Matric tension at the supplied water content. (kPa) + + Models: + $h(\theta)$: Linear wet-range segment + + Notes: + Prediction target: Matric tension for water content between theta_33 and theta_s. + At tensions below air entry, Equation 13 fixes water content at theta_s. + + Warning: + Use only for the 33 kPa to air-entry segment defined by the source. + + """ + if out is None: + values = _calc_ptf_saxton2006_tension_wet(theta, theta_33, theta_s, air_entry_tension) + else: + values = _calc_ptf_saxton2006_tension_wet( + theta, theta_33, theta_s, air_entry_tension, out=out + ) + + return values + + +@overload +def calc_conductivity_saxton2006( + *, theta: float, theta_s: float, saturated_conductivity: float, conductivity_lambda: float +) -> floating: ... + + +@overload +def calc_conductivity_saxton2006( + *, + theta: ArrayLike, + theta_s: ArrayLike, + saturated_conductivity: ArrayLike, + conductivity_lambda: ArrayLike, + out: NDArray[floating] | None = None, +) -> NDArray[floating]: ... + + +def calc_conductivity_saxton2006( + *, + theta: float | ArrayLike, + theta_s: float | ArrayLike, + saturated_conductivity: float | ArrayLike, + conductivity_lambda: float | ArrayLike, + out: NDArray[floating] | None = None, +) -> floating | NDArray[floating]: + """Estimate unsaturated hydraulic conductivity from water content. + + Arguments: + theta: Volumetric water content. (m^3/m^3) + theta_s: Saturated volumetric water content. (m^3/m^3) + saturated_conductivity: Saturated hydraulic conductivity of the matric soil. (mm/h) + conductivity_lambda: Inverse of retention exponent B. (dimensionless) + out: Optional output arrays for in-place calculation. + + Returns: + conductivity: Unsaturated hydraulic conductivity at the supplied water content. (mm/h) + + Models: + $k(h)$: Campbell unsaturated conductivity model + + Notes: + Prediction target: Unsaturated hydraulic conductivity of the matric soil. + + Warning: + The equation does not include residual water content. + + """ + if out is None: + values = _calc_ptf_saxton2006_conductivity( + theta, theta_s, saturated_conductivity, conductivity_lambda + ) + else: + values = _calc_ptf_saxton2006_conductivity( + theta, theta_s, saturated_conductivity, conductivity_lambda, out=out + ) + + return values + + +@overload +def calc_gravel_adjustment_saxton2006( + *, + gravel_weight_fraction: float, + matric_density: float, + plant_available_water: float, + saturated_conductivity: float, +) -> Saxton2006GravelResult[floating]: ... + + +@overload +def calc_gravel_adjustment_saxton2006( + *, + gravel_weight_fraction: ArrayLike, + matric_density: ArrayLike, + plant_available_water: ArrayLike, + saturated_conductivity: ArrayLike, + out: Saxton2006GravelResult[NDArray[floating]] | None = None, +) -> Saxton2006GravelResult[NDArray[floating]]: ... + + +def calc_gravel_adjustment_saxton2006( + *, + gravel_weight_fraction: float | ArrayLike, + matric_density: float | ArrayLike, + plant_available_water: float | ArrayLike, + saturated_conductivity: float | ArrayLike, + out: Saxton2006GravelResult[NDArray[floating]] | None = None, +) -> Saxton2006GravelResult[floating] | Saxton2006GravelResult[NDArray[floating]]: + r"""Adjust matric-soil properties for gravel content. + + Arguments: + gravel_weight_fraction: Gravel mass fraction of the bulk soil. (g/g) + matric_density: Dry bulk density of the fine-earth matric soil. (g/cm^3) + plant_available_water: Plant-available water of the matric soil. (m^3/m^3) + saturated_conductivity: Saturated hydraulic conductivity of the matric soil. (mm/h) + out: Optional output arrays for in-place calculation. + + Returns: + Saxton2006GravelResult: Results grouped by result attributes. + + Models: + $h(\theta)$: Gravel reduction of plant-available water + $k(h)$: Peck-Watson conductivity reduction + + Notes: + Prediction target: Gravel volume, bulk density, available water, and saturated conductivity. + Gravel comprises particles larger than 2 mm. + Gravel density is fixed at 2.65 g/cm^3 in the source equations. + + Warning: + The conductivity correction does not represent extra macropores sometimes found in gravelly + soils. + + """ + if out is None: + values = _calc_ptf_saxton2006_gravel( + gravel_weight_fraction, matric_density, plant_available_water, saturated_conductivity + ) + else: + values = _calc_ptf_saxton2006_gravel( + gravel_weight_fraction, + matric_density, + plant_available_water, + saturated_conductivity, + out=tuple(out), + ) + + return Saxton2006GravelResult(*values) + + +@overload +def calc_osmotic_potential_saxton2006( + *, electrical_conductivity: float, theta: float, theta_s: float +) -> Saxton2006SalinityResult[floating]: ... + + +@overload +def calc_osmotic_potential_saxton2006( + *, + electrical_conductivity: ArrayLike, + theta: ArrayLike, + theta_s: ArrayLike, + out: Saxton2006SalinityResult[NDArray[floating]] | None = None, +) -> Saxton2006SalinityResult[NDArray[floating]]: ... + + +def calc_osmotic_potential_saxton2006( + *, + electrical_conductivity: float | ArrayLike, + theta: float | ArrayLike, + theta_s: float | ArrayLike, + out: Saxton2006SalinityResult[NDArray[floating]] | None = None, +) -> Saxton2006SalinityResult[floating] | Saxton2006SalinityResult[NDArray[floating]]: + r"""Estimate saturated and moisture-adjusted osmotic potential. + + Arguments: + electrical_conductivity: Electrical conductivity of a saturated soil extract. (dS/m) + theta: Current volumetric water content. (m^3/m^3) + theta_s: Saturated volumetric water content. (m^3/m^3) + out: Optional output arrays for in-place calculation. + + Returns: + Saxton2006SalinityResult: Results grouped by result attributes. + + Models: + $h(\theta)$: Matric-plus-osmotic tension adjustment + + Notes: + Prediction target: Osmotic potential from saturated-extract electrical conductivity. + The moisture adjustment assumes dissolved salt quantity remains near constant as water + content falls. + + Warning: + Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity effects. + + """ + if out is None: + values = _calc_ptf_saxton2006_salinity(electrical_conductivity, theta, theta_s) + else: + values = _calc_ptf_saxton2006_salinity( + electrical_conductivity, theta, theta_s, out=tuple(out) + ) + + return Saxton2006SalinityResult(*values) diff --git a/targets/ptfkit-py/tests/test_oosterveld1980.py b/targets/ptfkit-py/tests/test_oosterveld1980.py new file mode 100644 index 0000000..2a4a4b3 --- /dev/null +++ b/targets/ptfkit-py/tests/test_oosterveld1980.py @@ -0,0 +1,196 @@ +# @generated by ptfkit-codegen; DO NOT EDIT. +from __future__ import annotations + +import pytest + +from _helpers import prepare_vector_case +from ptfkit.oosterveld1980 import ( + calc_ptf_oosterveld1980_available_water, + calc_ptf_oosterveld1980_field_capacity, + calc_ptf_oosterveld1980_field_capacity_tension, + calc_ptf_oosterveld1980_retention, + calc_ptf_oosterveld1980_wilting_point, +) + + +CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY_TENSION = [ + ({'clay': 6.6}, {'field_capacity_tension': 11.8540994164288}, 1e-12, 1e-12), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY_TENSION +) +def test_calc_ptf_oosterveld1980_field_capacity_tension_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_ptf_oosterveld1980_field_capacity_tension(**inputs) + + assert result == pytest.approx(expected['field_capacity_tension'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_field_capacity_tension_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY_TENSION + ) + result = calc_ptf_oosterveld1980_field_capacity_tension(**inputs, out=None) + assert result[0] == pytest.approx(expected['field_capacity_tension'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_field_capacity_tension_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY_TENSION + ) + result = calc_ptf_oosterveld1980_field_capacity_tension(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['field_capacity_tension'], rel=rtol, abs=atol) + + +CASES_CALC_PTF_OOSTERVELD1980_RETENTION = [ + ( + {'clay': 6.6, 'mean_depth': 105.0, 'sand': 86.3, 'tension': 11.8}, + {'moisture_content': 8.2782468100413}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_PTF_OOSTERVELD1980_RETENTION +) +def test_calc_ptf_oosterveld1980_retention_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_ptf_oosterveld1980_retention(**inputs) + + assert result == pytest.approx(expected['moisture_content'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_retention_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_RETENTION + ) + result = calc_ptf_oosterveld1980_retention(**inputs, out=None) + assert result[0] == pytest.approx(expected['moisture_content'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_retention_out(): + inputs, expected, rtol, atol, out = prepare_vector_case(CASES_CALC_PTF_OOSTERVELD1980_RETENTION) + result = calc_ptf_oosterveld1980_retention(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['moisture_content'], rel=rtol, abs=atol) + + +CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY = [ + ( + {'clay': 6.6, 'mean_depth': 105.0, 'sand': 86.3}, + {'field_capacity_moisture': 8.14707947394088}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY +) +def test_calc_ptf_oosterveld1980_field_capacity_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_ptf_oosterveld1980_field_capacity(**inputs) + + assert result == pytest.approx(expected['field_capacity_moisture'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_field_capacity_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY + ) + result = calc_ptf_oosterveld1980_field_capacity(**inputs, out=None) + assert result[0] == pytest.approx(expected['field_capacity_moisture'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_field_capacity_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_FIELD_CAPACITY + ) + result = calc_ptf_oosterveld1980_field_capacity(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['field_capacity_moisture'], rel=rtol, abs=atol) + + +CASES_CALC_PTF_OOSTERVELD1980_WILTING_POINT = [ + ( + {'clay': 6.6, 'mean_depth': 105.0, 'sand': 86.3}, + {'wilting_point_moisture': 1.3942}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_PTF_OOSTERVELD1980_WILTING_POINT +) +def test_calc_ptf_oosterveld1980_wilting_point_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_ptf_oosterveld1980_wilting_point(**inputs) + + assert result == pytest.approx(expected['wilting_point_moisture'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_wilting_point_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_WILTING_POINT + ) + result = calc_ptf_oosterveld1980_wilting_point(**inputs, out=None) + assert result[0] == pytest.approx(expected['wilting_point_moisture'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_wilting_point_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_WILTING_POINT + ) + result = calc_ptf_oosterveld1980_wilting_point(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['wilting_point_moisture'], rel=rtol, abs=atol) + + +CASES_CALC_PTF_OOSTERVELD1980_AVAILABLE_WATER = [ + ( + {'clay': 6.6, 'mean_depth': 105.0, 'sand': 86.3}, + {'available_moisture': 6.75287947394088}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_PTF_OOSTERVELD1980_AVAILABLE_WATER +) +def test_calc_ptf_oosterveld1980_available_water_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_ptf_oosterveld1980_available_water(**inputs) + + assert result == pytest.approx(expected['available_moisture'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_available_water_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_AVAILABLE_WATER + ) + result = calc_ptf_oosterveld1980_available_water(**inputs, out=None) + assert result[0] == pytest.approx(expected['available_moisture'], rel=rtol, abs=atol) + + +def test_calc_ptf_oosterveld1980_available_water_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_PTF_OOSTERVELD1980_AVAILABLE_WATER + ) + result = calc_ptf_oosterveld1980_available_water(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['available_moisture'], rel=rtol, abs=atol) diff --git a/targets/ptfkit-py/tests/test_saxton2006.py b/targets/ptfkit-py/tests/test_saxton2006.py new file mode 100644 index 0000000..3602c9a --- /dev/null +++ b/targets/ptfkit-py/tests/test_saxton2006.py @@ -0,0 +1,436 @@ +# @generated by ptfkit-codegen; DO NOT EDIT. +from __future__ import annotations + +import pytest + +from _helpers import prepare_vector_case +from ptfkit.saxton2006 import ( + Saxton2006DensityResult, + Saxton2006GravelResult, + Saxton2006PTFResult, + Saxton2006SalinityResult, + calc_conductivity_saxton2006, + calc_density_adjustment_saxton2006, + calc_gravel_adjustment_saxton2006, + calc_osmotic_potential_saxton2006, + calc_ptf_saxton2006, + calc_tension_dry_saxton2006, + calc_tension_wet_saxton2006, +) + + +CASES_CALC_PTF_SAXTON2006 = [ + ( + {'clay': 0.05, 'organic_matter': 2.5, 'sand': 0.88}, + { + 'air_entry_tension': 0.5986789729513293, + 'conductivity_lambda': 0.18776158355467926, + 'normal_density': 1.4264356197312436, + 'plant_available_water': 0.05260734364858702, + 'retention_a': 0.00018076452552206025, + 'retention_b': 5.325903100453899, + 'saturated_conductivity': 108.1478278507403, + 'theta_1500': 0.05022058, + 'theta_33': 0.10282792364858702, + 'theta_s': 0.46172240764858724, + }, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize(('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_PTF_SAXTON2006) +def test_calc_ptf_saxton2006_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_ptf_saxton2006(**inputs) + + assert result.theta_1500 == pytest.approx(expected['theta_1500'], rel=rtol, abs=atol) + assert result.theta_33 == pytest.approx(expected['theta_33'], rel=rtol, abs=atol) + assert result.theta_s == pytest.approx(expected['theta_s'], rel=rtol, abs=atol) + assert result.plant_available_water == pytest.approx( + expected['plant_available_water'], rel=rtol, abs=atol + ) + assert result.air_entry_tension == pytest.approx( + expected['air_entry_tension'], rel=rtol, abs=atol + ) + assert result.retention_a == pytest.approx(expected['retention_a'], rel=rtol, abs=atol) + assert result.retention_b == pytest.approx(expected['retention_b'], rel=rtol, abs=atol) + assert result.conductivity_lambda == pytest.approx( + expected['conductivity_lambda'], rel=rtol, abs=atol + ) + assert result.saturated_conductivity == pytest.approx( + expected['saturated_conductivity'], rel=rtol, abs=atol + ) + assert result.normal_density == pytest.approx(expected['normal_density'], rel=rtol, abs=atol) + + +def test_calc_ptf_saxton2006_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_PTF_SAXTON2006, Saxton2006PTFResult + ) + result = calc_ptf_saxton2006(**inputs, out=None) + assert result.theta_1500[0] == pytest.approx(expected['theta_1500'], rel=rtol, abs=atol) + assert result.theta_33[0] == pytest.approx(expected['theta_33'], rel=rtol, abs=atol) + assert result.theta_s[0] == pytest.approx(expected['theta_s'], rel=rtol, abs=atol) + assert result.plant_available_water[0] == pytest.approx( + expected['plant_available_water'], rel=rtol, abs=atol + ) + assert result.air_entry_tension[0] == pytest.approx( + expected['air_entry_tension'], rel=rtol, abs=atol + ) + assert result.retention_a[0] == pytest.approx(expected['retention_a'], rel=rtol, abs=atol) + assert result.retention_b[0] == pytest.approx(expected['retention_b'], rel=rtol, abs=atol) + assert result.conductivity_lambda[0] == pytest.approx( + expected['conductivity_lambda'], rel=rtol, abs=atol + ) + assert result.saturated_conductivity[0] == pytest.approx( + expected['saturated_conductivity'], rel=rtol, abs=atol + ) + assert result.normal_density[0] == pytest.approx(expected['normal_density'], rel=rtol, abs=atol) + + +def test_calc_ptf_saxton2006_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_PTF_SAXTON2006, Saxton2006PTFResult + ) + result = calc_ptf_saxton2006(**inputs, out=out) + for actual, expected_out in zip(result, out, strict=True): + assert actual is expected_out + assert result.theta_1500[0] == pytest.approx(expected['theta_1500'], rel=rtol, abs=atol) + assert result.theta_33[0] == pytest.approx(expected['theta_33'], rel=rtol, abs=atol) + assert result.theta_s[0] == pytest.approx(expected['theta_s'], rel=rtol, abs=atol) + assert result.plant_available_water[0] == pytest.approx( + expected['plant_available_water'], rel=rtol, abs=atol + ) + assert result.air_entry_tension[0] == pytest.approx( + expected['air_entry_tension'], rel=rtol, abs=atol + ) + assert result.retention_a[0] == pytest.approx(expected['retention_a'], rel=rtol, abs=atol) + assert result.retention_b[0] == pytest.approx(expected['retention_b'], rel=rtol, abs=atol) + assert result.conductivity_lambda[0] == pytest.approx( + expected['conductivity_lambda'], rel=rtol, abs=atol + ) + assert result.saturated_conductivity[0] == pytest.approx( + expected['saturated_conductivity'], rel=rtol, abs=atol + ) + assert result.normal_density[0] == pytest.approx(expected['normal_density'], rel=rtol, abs=atol) + + +CASES_CALC_DENSITY_ADJUSTMENT_SAXTON2006 = [ + ( + { + 'density_factor': 1.1, + 'normal_density': 1.4264356197312436, + 'theta_33': 0.10282792364858702, + 'theta_s': 0.46172240764858724, + }, + { + 'adjusted_density': 1.569079181704368, + 'adjusted_theta_33': 0.09206237180155877, + 'adjusted_theta_s': 0.407894648413446, + 'adjusted_theta_s_minus_33': 0.3158322766118873, + }, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_DENSITY_ADJUSTMENT_SAXTON2006 +) +def test_calc_density_adjustment_saxton2006_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_density_adjustment_saxton2006(**inputs) + + assert result.adjusted_density == pytest.approx( + expected['adjusted_density'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_s == pytest.approx( + expected['adjusted_theta_s'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_33 == pytest.approx( + expected['adjusted_theta_33'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_s_minus_33 == pytest.approx( + expected['adjusted_theta_s_minus_33'], rel=rtol, abs=atol + ) + + +def test_calc_density_adjustment_saxton2006_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_DENSITY_ADJUSTMENT_SAXTON2006, Saxton2006DensityResult + ) + result = calc_density_adjustment_saxton2006(**inputs, out=None) + assert result.adjusted_density[0] == pytest.approx( + expected['adjusted_density'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_s[0] == pytest.approx( + expected['adjusted_theta_s'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_33[0] == pytest.approx( + expected['adjusted_theta_33'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_s_minus_33[0] == pytest.approx( + expected['adjusted_theta_s_minus_33'], rel=rtol, abs=atol + ) + + +def test_calc_density_adjustment_saxton2006_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_DENSITY_ADJUSTMENT_SAXTON2006, Saxton2006DensityResult + ) + result = calc_density_adjustment_saxton2006(**inputs, out=out) + for actual, expected_out in zip(result, out, strict=True): + assert actual is expected_out + assert result.adjusted_density[0] == pytest.approx( + expected['adjusted_density'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_s[0] == pytest.approx( + expected['adjusted_theta_s'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_33[0] == pytest.approx( + expected['adjusted_theta_33'], rel=rtol, abs=atol + ) + assert result.adjusted_theta_s_minus_33[0] == pytest.approx( + expected['adjusted_theta_s_minus_33'], rel=rtol, abs=atol + ) + + +CASES_CALC_TENSION_DRY_SAXTON2006 = [ + ( + {'theta': 0.07652425182429351, 'theta_1500': 0.05022058, 'theta_33': 0.10282792364858702}, + {'tension': 159.18094591362183}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize(('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_TENSION_DRY_SAXTON2006) +def test_calc_tension_dry_saxton2006_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_tension_dry_saxton2006(**inputs) + + assert result == pytest.approx(expected['tension'], rel=rtol, abs=atol) + + +def test_calc_tension_dry_saxton2006_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case(CASES_CALC_TENSION_DRY_SAXTON2006) + result = calc_tension_dry_saxton2006(**inputs, out=None) + assert result[0] == pytest.approx(expected['tension'], rel=rtol, abs=atol) + + +def test_calc_tension_dry_saxton2006_out(): + inputs, expected, rtol, atol, out = prepare_vector_case(CASES_CALC_TENSION_DRY_SAXTON2006) + result = calc_tension_dry_saxton2006(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['tension'], rel=rtol, abs=atol) + + +CASES_CALC_TENSION_WET_SAXTON2006 = [ + ( + { + 'air_entry_tension': 0.5986789729513293, + 'theta': 0.2, + 'theta_33': 0.10282792364858702, + 'theta_s': 0.46172240764858724, + }, + {'tension': 24.227216407352152}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize(('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_TENSION_WET_SAXTON2006) +def test_calc_tension_wet_saxton2006_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_tension_wet_saxton2006(**inputs) + + assert result == pytest.approx(expected['tension'], rel=rtol, abs=atol) + + +def test_calc_tension_wet_saxton2006_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case(CASES_CALC_TENSION_WET_SAXTON2006) + result = calc_tension_wet_saxton2006(**inputs, out=None) + assert result[0] == pytest.approx(expected['tension'], rel=rtol, abs=atol) + + +def test_calc_tension_wet_saxton2006_out(): + inputs, expected, rtol, atol, out = prepare_vector_case(CASES_CALC_TENSION_WET_SAXTON2006) + result = calc_tension_wet_saxton2006(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['tension'], rel=rtol, abs=atol) + + +CASES_CALC_CONDUCTIVITY_SAXTON2006 = [ + ( + { + 'conductivity_lambda': 0.18776158355467926, + 'saturated_conductivity': 108.1478278507403, + 'theta': 0.3, + 'theta_s': 0.46172240764858724, + }, + {'conductivity': 0.3003203142764693}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize(('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_CONDUCTIVITY_SAXTON2006) +def test_calc_conductivity_saxton2006_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_conductivity_saxton2006(**inputs) + + assert result == pytest.approx(expected['conductivity'], rel=rtol, abs=atol) + + +def test_calc_conductivity_saxton2006_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case(CASES_CALC_CONDUCTIVITY_SAXTON2006) + result = calc_conductivity_saxton2006(**inputs, out=None) + assert result[0] == pytest.approx(expected['conductivity'], rel=rtol, abs=atol) + + +def test_calc_conductivity_saxton2006_out(): + inputs, expected, rtol, atol, out = prepare_vector_case(CASES_CALC_CONDUCTIVITY_SAXTON2006) + result = calc_conductivity_saxton2006(**inputs, out=out) + assert result is out + assert result[0] == pytest.approx(expected['conductivity'], rel=rtol, abs=atol) + + +CASES_CALC_GRAVEL_ADJUSTMENT_SAXTON2006 = [ + ( + { + 'gravel_weight_fraction': 0.2, + 'matric_density': 1.4264356197312436, + 'plant_available_water': 0.05260734364858702, + 'saturated_conductivity': 108.1478278507403, + }, + { + 'bulk_density': 1.5715605653291098, + 'bulk_plant_available_water': 0.04636767370708995, + 'bulk_saturated_conductivity': 97.54880510583706, + 'gravel_volume_fraction': 0.11860834455314037, + }, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_GRAVEL_ADJUSTMENT_SAXTON2006 +) +def test_calc_gravel_adjustment_saxton2006_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_gravel_adjustment_saxton2006(**inputs) + + assert result.gravel_volume_fraction == pytest.approx( + expected['gravel_volume_fraction'], rel=rtol, abs=atol + ) + assert result.bulk_density == pytest.approx(expected['bulk_density'], rel=rtol, abs=atol) + assert result.bulk_plant_available_water == pytest.approx( + expected['bulk_plant_available_water'], rel=rtol, abs=atol + ) + assert result.bulk_saturated_conductivity == pytest.approx( + expected['bulk_saturated_conductivity'], rel=rtol, abs=atol + ) + + +def test_calc_gravel_adjustment_saxton2006_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_GRAVEL_ADJUSTMENT_SAXTON2006, Saxton2006GravelResult + ) + result = calc_gravel_adjustment_saxton2006(**inputs, out=None) + assert result.gravel_volume_fraction[0] == pytest.approx( + expected['gravel_volume_fraction'], rel=rtol, abs=atol + ) + assert result.bulk_density[0] == pytest.approx(expected['bulk_density'], rel=rtol, abs=atol) + assert result.bulk_plant_available_water[0] == pytest.approx( + expected['bulk_plant_available_water'], rel=rtol, abs=atol + ) + assert result.bulk_saturated_conductivity[0] == pytest.approx( + expected['bulk_saturated_conductivity'], rel=rtol, abs=atol + ) + + +def test_calc_gravel_adjustment_saxton2006_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_GRAVEL_ADJUSTMENT_SAXTON2006, Saxton2006GravelResult + ) + result = calc_gravel_adjustment_saxton2006(**inputs, out=out) + for actual, expected_out in zip(result, out, strict=True): + assert actual is expected_out + assert result.gravel_volume_fraction[0] == pytest.approx( + expected['gravel_volume_fraction'], rel=rtol, abs=atol + ) + assert result.bulk_density[0] == pytest.approx(expected['bulk_density'], rel=rtol, abs=atol) + assert result.bulk_plant_available_water[0] == pytest.approx( + expected['bulk_plant_available_water'], rel=rtol, abs=atol + ) + assert result.bulk_saturated_conductivity[0] == pytest.approx( + expected['bulk_saturated_conductivity'], rel=rtol, abs=atol + ) + + +CASES_CALC_OSMOTIC_POTENTIAL_SAXTON2006 = [ + ( + {'electrical_conductivity': 4.0, 'theta': 0.3, 'theta_s': 0.46172240764858724}, + {'osmotic_potential': 221.6267556713219, 'saturated_osmotic_potential': 144.0}, + 1e-12, + 1e-12, + ), +] + + +@pytest.mark.parametrize( + ('inputs', 'expected', 'rtol', 'atol'), CASES_CALC_OSMOTIC_POTENTIAL_SAXTON2006 +) +def test_calc_osmotic_potential_saxton2006_golden( + inputs: dict[str, float], expected: dict[str, float], rtol: float, atol: float +): + result = calc_osmotic_potential_saxton2006(**inputs) + + assert result.saturated_osmotic_potential == pytest.approx( + expected['saturated_osmotic_potential'], rel=rtol, abs=atol + ) + assert result.osmotic_potential == pytest.approx( + expected['osmotic_potential'], rel=rtol, abs=atol + ) + + +def test_calc_osmotic_potential_saxton2006_array(): + inputs, expected, rtol, atol, _out = prepare_vector_case( + CASES_CALC_OSMOTIC_POTENTIAL_SAXTON2006, Saxton2006SalinityResult + ) + result = calc_osmotic_potential_saxton2006(**inputs, out=None) + assert result.saturated_osmotic_potential[0] == pytest.approx( + expected['saturated_osmotic_potential'], rel=rtol, abs=atol + ) + assert result.osmotic_potential[0] == pytest.approx( + expected['osmotic_potential'], rel=rtol, abs=atol + ) + + +def test_calc_osmotic_potential_saxton2006_out(): + inputs, expected, rtol, atol, out = prepare_vector_case( + CASES_CALC_OSMOTIC_POTENTIAL_SAXTON2006, Saxton2006SalinityResult + ) + result = calc_osmotic_potential_saxton2006(**inputs, out=out) + for actual, expected_out in zip(result, out, strict=True): + assert actual is expected_out + assert result.saturated_osmotic_potential[0] == pytest.approx( + expected['saturated_osmotic_potential'], rel=rtol, abs=atol + ) + assert result.osmotic_potential[0] == pytest.approx( + expected['osmotic_potential'], rel=rtol, abs=atol + ) diff --git a/targets/ptfkit-rs/src/oosterveld1980.rs b/targets/ptfkit-rs/src/oosterveld1980.rs new file mode 100644 index 0000000..c3b60e8 --- /dev/null +++ b/targets/ptfkit-rs/src/oosterveld1980.rs @@ -0,0 +1,257 @@ +// @generated by ptfkit-codegen; DO NOT EDIT. + +#![doc = r"Oosterveld and Chang (1980), soil-moisture retention from texture and depth. + +# Reference + +Oosterveld, M., & Chang, C. (1980). Empirical relations between laboratory determinations of +soil texture and moisture retention. Canadian Agricultural Engineering, 22, 149-151. + +# Territory + +Southern Alberta, Canada + +# Dataset + +1,137 pressure-plate analyses on 298 soil samples from the Lethbridge Research Station +laboratory; the field-capacity tension regression used 134 samples."] + +#[doc = r"Estimate field-capacity tension from clay content. + +# Arguments + + * clay: Clay content in percent by weight; the field-capacity tension experiment covered 2% to + 72% clay. (% mass) + +# Returns + + * field_capacity_tension: Predicted tension corresponding to cylinder-method field capacity. + (kPa) + +# Models + + * h(theta): Field-capacity tension point estimate + +# Notes + +Prediction target: Tension at which pressure-plate moisture content equals field capacity +determined by the cylinder method. +Equation 1 reports r = 0.67 and n = 134."] +#[must_use] +pub fn calc_ptf_oosterveld1980_field_capacity_tension(clay: f64) -> f64 { + 5.356f64 * clay.powf(0.421f64) +} +#[cfg(test)] +mod calc_ptf_oosterveld1980_field_capacity_tension_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_1_loamy_sand() { + let result = calc_ptf_oosterveld1980_field_capacity_tension(6.6f64); + assert_close( + result, + 11.8540994164288f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Estimate gravimetric soil-moisture content from texture, depth, and tension. + +# Arguments + + * clay: Clay content in percent by weight. (% mass) + * sand: Sand content in percent by weight. (% mass) + * mean_depth: Mean depth of the soil sample. (cm) + * tension: Soil-moisture tension. (kPa) + +# Returns + + * moisture_content: Predicted soil-moisture content in percent by weight. (% mass) + +# Models + + * h(theta): Empirical power-law moisture-retention relation + +# Notes + +Prediction target: Gravimetric soil-moisture content at a specified tension. +Equation 2 reports r = 0.96 and n = 1,137. + +# Warnings + +The paper reports slight inaccuracy at very high clay content and high tension."] +#[must_use] +pub fn calc_ptf_oosterveld1980_retention( + clay: f64, + sand: f64, + mean_depth: f64, + tension: f64, +) -> f64 { + (35.367f64 + 0.644f64 * clay - 0.251f64 * sand - 0.045f64 * mean_depth) + * tension.powf(-0.190f64) +} +#[cfg(test)] +mod calc_ptf_oosterveld1980_retention_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_1_loamy_sand_inputs() { + let result = calc_ptf_oosterveld1980_retention(6.6f64, 86.3f64, 105f64, 11.8f64); + assert_close( + result, + 8.2782468100413f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Estimate gravimetric soil-moisture content at field capacity. + +# Arguments + + * clay: Clay content in percent by weight; the field-capacity tension experiment covered 2% to + 72% clay. (% mass) + * sand: Sand content in percent by weight. (% mass) + * mean_depth: Mean depth of the soil sample. (cm) + +# Returns + + * field_capacity_moisture: Predicted soil-moisture content at field capacity in percent by + weight. (% mass) + +# Models + + * h(theta): Field-capacity point estimate derived from Equations 1 and 2 + +# Notes + +Prediction target: Gravimetric soil-moisture content at field capacity. +Equation 3 is obtained by substituting the Equation 1 field-capacity tension into Equation 2."] +#[must_use] +pub fn calc_ptf_oosterveld1980_field_capacity(clay: f64, sand: f64, mean_depth: f64) -> f64 { + (25.713f64 + 0.469f64 * clay - 0.184f64 * sand - 0.0329f64 * mean_depth) * clay.powf(-0.080f64) +} +#[cfg(test)] +mod calc_ptf_oosterveld1980_field_capacity_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_1_loamy_sand() { + let result = calc_ptf_oosterveld1980_field_capacity(6.6f64, 86.3f64, 105f64); + assert_close( + result, + 8.14707947394088f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Estimate gravimetric soil-moisture content at the 1500 kPa wilting point. + +# Arguments + + * clay: Clay content in percent by weight. (% mass) + * sand: Sand content in percent by weight. (% mass) + * mean_depth: Mean depth of the soil sample. (cm) + +# Returns + + * wilting_point_moisture: Predicted soil-moisture content at 1500 kPa in percent by weight. (% + mass) + +# Models + + * h(theta): Wilting-point estimate at 1500 kPa + +# Notes + +Prediction target: Gravimetric soil-moisture content at 1500 kPa. +Equation 4 reports r = 0.96 and n = 298. +The paper takes moisture content at 1500 kPa as the wilting point."] +#[must_use] +pub fn calc_ptf_oosterveld1980_wilting_point(clay: f64, sand: f64, mean_depth: f64) -> f64 { + 4.035f64 + 0.299f64 * clay - 0.034f64 * sand - 0.016f64 * mean_depth +} +#[cfg(test)] +mod calc_ptf_oosterveld1980_wilting_point_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_1_loamy_sand() { + let result = calc_ptf_oosterveld1980_wilting_point(6.6f64, 86.3f64, 105f64); + assert_close(result, 1.3942f64, 0.000000000001f64, 0.000000000001f64); + } +} +#[doc = r"Estimate available gravimetric soil moisture between field capacity and wilting point. + +# Arguments + + * clay: Clay content in percent by weight; the field-capacity tension experiment covered 2% to + 72% clay. (% mass) + * sand: Sand content in percent by weight. (% mass) + * mean_depth: Mean depth of the soil sample. (cm) + +# Returns + + * available_moisture: Predicted field-capacity moisture minus predicted wilting-point + moisture, in percentage points by weight. (% mass) + +# Models + + * h(theta): Available-moisture point estimate from Equations 3 and 4 + +# Notes + +Prediction target: Difference between gravimetric moisture content at field capacity and at the +1500 kPa wilting point. +The paper defines available soil water by subtracting Equation 4 from Equation 3."] +#[must_use] +pub fn calc_ptf_oosterveld1980_available_water(clay: f64, sand: f64, mean_depth: f64) -> f64 { + let field_capacity_moisture = + (25.713f64 + 0.469f64 * clay - 0.184f64 * sand - 0.0329f64 * mean_depth) + * clay.powf(-0.080f64); + let wilting_point_moisture = + 4.035f64 + 0.299f64 * clay - 0.034f64 * sand - 0.016f64 * mean_depth; + field_capacity_moisture - wilting_point_moisture +} +#[cfg(test)] +mod calc_ptf_oosterveld1980_available_water_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_1_loamy_sand() { + let result = calc_ptf_oosterveld1980_available_water(6.6f64, 86.3f64, 105f64); + assert_close( + result, + 6.75287947394088f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} diff --git a/targets/ptfkit-rs/src/saxton2006.rs b/targets/ptfkit-rs/src/saxton2006.rs new file mode 100644 index 0000000..73c4e4e --- /dev/null +++ b/targets/ptfkit-rs/src/saxton2006.rs @@ -0,0 +1,649 @@ +// @generated by ptfkit-codegen; DO NOT EDIT. + +#![doc = r"Saxton and Rawls (2006), soil water characteristics from texture and organic matter. + +# Reference + +Saxton, K. E., & Rawls, W. J. (2006). Soil water characteristic estimates by texture and organic +matter for hydrologic solutions. Soil Science Society of America Journal, 70(5), 1569-1578. +DOI: 10.2136/ssaj2005.0117 (https://doi.org/10.2136/ssaj2005.0117) + +# Territory + +United States + +# Dataset + +1,722 mineral-soil A-horizon samples retained from the USDA/NRCS National Soil Characterization +database after exclusions from 2,149 samples."] + +#[doc = r"Results returned by `calc_ptf_saxton2006`."] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Saxton2006PTFResult { + #[doc = r"Volumetric water content at 1500 kPa matric tension. (m^3/m^3)"] + pub theta_1500: f64, + #[doc = r"Volumetric water content at 33 kPa matric tension. (m^3/m^3)"] + pub theta_33: f64, + #[doc = r"Saturated volumetric water content at normal density. (m^3/m^3)"] + pub theta_s: f64, + #[doc = r"Difference between the 33 and 1500 kPa water contents. (m^3/m^3)"] + pub plant_available_water: f64, + #[doc = r"Air-entry or bubbling-pressure tension. (kPa)"] + pub air_entry_tension: f64, + #[doc = r"Coefficient A of the dry-range moisture-tension curve. (kPa)"] + pub retention_a: f64, + #[doc = r"Exponent B of the dry-range moisture-tension curve. (dimensionless)"] + pub retention_b: f64, + #[doc = r"Inverse of retention exponent B. (dimensionless)"] + pub conductivity_lambda: f64, + #[doc = r"Saturated hydraulic conductivity of the matric soil. (mm/h)"] + pub saturated_conductivity: f64, + #[doc = r"Normal dry bulk density assuming particle density 2.65 g/cm^3. (g/cm^3)"] + pub normal_density: f64, +} +#[doc = r"Estimate soil water characteristics from sand, clay, and organic matter. + +# Arguments + + * sand: Sand mass fraction of the fine-earth soil. (g/g) + * clay: Clay mass fraction of the fine-earth soil. (g/g) + * organic_matter: Organic matter content on a mass percentage basis. (% mass) + +# Returns + +A [`Saxton2006PTFResult`]. + +# Models + + * h(theta): Campbell power function with a linear wet-range segment + * k(h): Campbell unsaturated conductivity model + +# Notes + +Prediction target: Water contents at 1500, 33, and 0 kPa, plant-available water, air-entry +tension, retention coefficients, normal density, and saturated hydraulic conductivity. +Sand and clay are decimal mass fractions, while organic matter is a percentage. +The 1500 and 33 kPa values are also termed wilting point and field capacity. + +# Warnings + +Do not apply the regression above 8% organic matter or 60% clay. +Sand and clay fractions must describe one soil and therefore must sum to at most 1. +These statistical-average estimates should be calibrated to local measurements when available."] +#[must_use] +pub fn calc_ptf_saxton2006(sand: f64, clay: f64, organic_matter: f64) -> Saxton2006PTFResult { + let theta_1500_preliminary = -0.024f64 * sand + + 0.487f64 * clay + + 0.006f64 * organic_matter + + 0.005f64 * sand * organic_matter + - 0.013f64 * clay * organic_matter + + 0.068f64 * sand * clay + + 0.031f64; + let theta_1500 = theta_1500_preliminary + 0.14f64 * theta_1500_preliminary - 0.02f64; + let theta_33_preliminary = -0.251f64 * sand + + 0.195f64 * clay + + 0.011f64 * organic_matter + + 0.006f64 * sand * organic_matter + - 0.027f64 * clay * organic_matter + + 0.452f64 * sand * clay + + 0.299f64; + let theta_33 = theta_33_preliminary + 1.283f64 * theta_33_preliminary.powi(2) + - 0.374f64 * theta_33_preliminary + - 0.015f64; + let theta_s_minus_33_preliminary = + 0.278f64 * sand + 0.034f64 * clay + 0.022f64 * organic_matter + - 0.018f64 * sand * organic_matter + - 0.027f64 * clay * organic_matter + - 0.584f64 * sand * clay + + 0.078f64; + let theta_s_minus_33 = + theta_s_minus_33_preliminary + 0.636f64 * theta_s_minus_33_preliminary - 0.107f64; + let theta_s = theta_33 + theta_s_minus_33 - 0.097f64 * sand + 0.043f64; + let plant_available_water = theta_33 - theta_1500; + let air_entry_preliminary = + -21.67f64 * sand - 27.93f64 * clay - 81.97f64 * theta_s_minus_33_preliminary + + 71.12f64 * sand * theta_s_minus_33_preliminary + + 8.29f64 * clay * theta_s_minus_33_preliminary + + 14.05f64 * sand * clay + + 27.16f64; + let air_entry_tension = air_entry_preliminary + 0.02f64 * air_entry_preliminary.powi(2) + - 0.113f64 * air_entry_preliminary + - 0.70f64; + let retention_b = (1500.0f64.ln() - 33.0f64.ln()) / (theta_33.ln() - theta_1500.ln()); + let retention_a = (33.0f64.ln() + retention_b * theta_33.ln()).exp(); + let conductivity_lambda = 1.0f64 / retention_b; + let saturated_conductivity = + 1930.0f64 * (theta_s - theta_33).powf(3.0f64 - conductivity_lambda); + let normal_density = (1.0f64 - theta_s) * 2.65f64; + Saxton2006PTFResult { + theta_1500, + theta_33, + theta_s, + plant_available_water, + air_entry_tension, + retention_a, + retention_b, + conductivity_lambda, + saturated_conductivity, + normal_density, + } +} +#[cfg(test)] +mod calc_ptf_saxton2006_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_3_sand() { + let result = calc_ptf_saxton2006(0.88f64, 0.05f64, 2.5f64); + assert_close( + result.theta_1500, + 0.05022058f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.theta_33, + 0.10282792364858702f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.theta_s, + 0.46172240764858724f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.plant_available_water, + 0.05260734364858702f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.air_entry_tension, + 0.5986789729513293f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.retention_a, + 0.00018076452552206025f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.retention_b, + 5.325903100453899f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.conductivity_lambda, + 0.18776158355467926f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.saturated_conductivity, + 108.1478278507403f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.normal_density, + 1.4264356197312436f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Results returned by `calc_ptf_saxton2006_density`."] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Saxton2006DensityResult { + #[doc = r"Density after applying the density factor. (g/cm^3)"] + pub adjusted_density: f64, + #[doc = r"Saturated water content after the density adjustment. (m^3/m^3)"] + pub adjusted_theta_s: f64, + #[doc = r"Water content at 33 kPa after the density adjustment. (m^3/m^3)"] + pub adjusted_theta_33: f64, + #[doc = r"Density-adjusted difference between saturation and 33 kPa. (m^3/m^3)"] + pub adjusted_theta_s_minus_33: f64, +} +#[doc = r"Adjust Saxton and Rawls water characteristics for soil density. + +# Arguments + + * normal_density: Normal dry bulk density estimated by the base model. (g/cm^3) + * theta_s: Saturated water content at normal density. (m^3/m^3) + * theta_33: Water content at 33 kPa and normal density. (m^3/m^3) + * density_factor: Multiplicative adjustment to normal density. (dimensionless) + +# Returns + +A [`Saxton2006DensityResult`]. + +# Models + + * h(theta): Density adjustment to the point estimates + +# Notes + +Prediction target: Density-adjusted saturation and 33 kPa water contents. +The source limits the saturation-minus-33 kPa difference to at least 0.005 m^3/m^3. + +# Warnings + +The source recommends density factors only from 0.9 to 1.3."] +#[must_use] +pub fn calc_ptf_saxton2006_density( + normal_density: f64, + theta_s: f64, + theta_33: f64, + density_factor: f64, +) -> Saxton2006DensityResult { + let adjusted_density = normal_density * density_factor; + let adjusted_theta_s = 1.0f64 - adjusted_density / 2.65f64; + let adjusted_theta_33 = theta_33 - 0.2f64 * (theta_s - adjusted_theta_s); + let adjusted_theta_s_minus_33 = (adjusted_theta_s - adjusted_theta_33).max(0.005f64); + Saxton2006DensityResult { + adjusted_density, + adjusted_theta_s, + adjusted_theta_33, + adjusted_theta_s_minus_33, + } +} +#[cfg(test)] +mod calc_ptf_saxton2006_density_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_3_sand_compacted() { + let result = calc_ptf_saxton2006_density( + 1.4264356197312436f64, + 0.46172240764858724f64, + 0.10282792364858702f64, + 1.1f64, + ); + assert_close( + result.adjusted_density, + 1.569079181704368f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.adjusted_theta_s, + 0.407894648413446f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.adjusted_theta_33, + 0.09206237180155877f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.adjusted_theta_s_minus_33, + 0.3158322766118873f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Estimate matric tension in the 1500 to 33 kPa segment. + +# Arguments + + * theta: Volumetric water content. (m^3/m^3) + * theta_1500: Volumetric water content at 1500 kPa. (m^3/m^3) + * theta_33: Volumetric water content at 33 kPa. (m^3/m^3) + +# Returns + + * tension: Matric tension at the supplied water content. (kPa) + +# Models + + * h(theta): Campbell power function + +# Notes + +Prediction target: Matric tension for water content between theta_1500 and theta_33. + +# Warnings + +Use only for the 1500 to 33 kPa segment defined by the source."] +#[must_use] +pub fn calc_ptf_saxton2006_tension_dry(theta: f64, theta_1500: f64, theta_33: f64) -> f64 { + let retention_b = (1500.0f64.ln() - 33.0f64.ln()) / (theta_33.ln() - theta_1500.ln()); + let retention_a = (33.0f64.ln() + retention_b * theta_33.ln()).exp(); + retention_a * theta.powf(-retention_b) +} +#[cfg(test)] +mod calc_ptf_saxton2006_tension_dry_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_3_sand_midpoint() { + let result = calc_ptf_saxton2006_tension_dry( + 0.07652425182429351f64, + 0.05022058f64, + 0.10282792364858702f64, + ); + assert_close( + result, + 159.18094591362183f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Estimate matric tension in the 33 kPa to air-entry segment. + +# Arguments + + * theta: Volumetric water content. (m^3/m^3) + * theta_33: Volumetric water content at 33 kPa. (m^3/m^3) + * theta_s: Saturated volumetric water content. (m^3/m^3) + * air_entry_tension: Air-entry tension estimated by the base model. (kPa) + +# Returns + + * tension: Matric tension at the supplied water content. (kPa) + +# Models + + * h(theta): Linear wet-range segment + +# Notes + +Prediction target: Matric tension for water content between theta_33 and theta_s. +At tensions below air entry, Equation 13 fixes water content at theta_s. + +# Warnings + +Use only for the 33 kPa to air-entry segment defined by the source."] +#[must_use] +pub fn calc_ptf_saxton2006_tension_wet( + theta: f64, + theta_33: f64, + theta_s: f64, + air_entry_tension: f64, +) -> f64 { + 33.0f64 - (theta - theta_33) * (33.0f64 - air_entry_tension) / (theta_s - theta_33) +} +#[cfg(test)] +mod calc_ptf_saxton2006_tension_wet_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_3_sand_theta_0_2() { + let result = calc_ptf_saxton2006_tension_wet( + 0.2f64, + 0.10282792364858702f64, + 0.46172240764858724f64, + 0.5986789729513293f64, + ); + assert_close( + result, + 24.227216407352152f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Estimate unsaturated hydraulic conductivity from water content. + +# Arguments + + * theta: Volumetric water content. (m^3/m^3) + * theta_s: Saturated volumetric water content. (m^3/m^3) + * saturated_conductivity: Saturated hydraulic conductivity of the matric soil. (mm/h) + * conductivity_lambda: Inverse of retention exponent B. (dimensionless) + +# Returns + + * conductivity: Unsaturated hydraulic conductivity at the supplied water content. (mm/h) + +# Models + + * k(h): Campbell unsaturated conductivity model + +# Notes + +Prediction target: Unsaturated hydraulic conductivity of the matric soil. + +# Warnings + +The equation does not include residual water content."] +#[must_use] +pub fn calc_ptf_saxton2006_conductivity( + theta: f64, + theta_s: f64, + saturated_conductivity: f64, + conductivity_lambda: f64, +) -> f64 { + saturated_conductivity * (theta / theta_s).powf(3.0f64 + 2.0f64 / conductivity_lambda) +} +#[cfg(test)] +mod calc_ptf_saxton2006_conductivity_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_3_sand_theta_0_3() { + let result = calc_ptf_saxton2006_conductivity( + 0.3f64, + 0.46172240764858724f64, + 108.1478278507403f64, + 0.18776158355467926f64, + ); + assert_close( + result, + 0.3003203142764693f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Results returned by `calc_ptf_saxton2006_gravel`."] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Saxton2006GravelResult { + #[doc = r"Gravel volume fraction of the bulk soil. (m^3/m^3)"] + pub gravel_volume_fraction: f64, + #[doc = r"Dry bulk density of matric soil plus gravel. (g/cm^3)"] + pub bulk_density: f64, + #[doc = r"Plant-available water on a bulk-soil volume basis. (m^3/m^3)"] + pub bulk_plant_available_water: f64, + #[doc = r"Saturated conductivity after the gravel reduction. (mm/h)"] + pub bulk_saturated_conductivity: f64, +} +#[doc = r"Adjust matric-soil properties for gravel content. + +# Arguments + + * gravel_weight_fraction: Gravel mass fraction of the bulk soil. (g/g) + * matric_density: Dry bulk density of the fine-earth matric soil. (g/cm^3) + * plant_available_water: Plant-available water of the matric soil. (m^3/m^3) + * saturated_conductivity: Saturated hydraulic conductivity of the matric soil. (mm/h) + +# Returns + +A [`Saxton2006GravelResult`]. + +# Models + + * h(theta): Gravel reduction of plant-available water + * k(h): Peck-Watson conductivity reduction + +# Notes + +Prediction target: Gravel volume, bulk density, available water, and saturated conductivity. +Gravel comprises particles larger than 2 mm. +Gravel density is fixed at 2.65 g/cm^3 in the source equations. + +# Warnings + +The conductivity correction does not represent extra macropores sometimes found in gravelly +soils."] +#[must_use] +pub fn calc_ptf_saxton2006_gravel( + gravel_weight_fraction: f64, + matric_density: f64, + plant_available_water: f64, + saturated_conductivity: f64, +) -> Saxton2006GravelResult { + let density_ratio = matric_density / 2.65f64; + let gravel_volume_fraction = density_ratio * gravel_weight_fraction + / (1.0f64 - gravel_weight_fraction * (1.0f64 - density_ratio)); + let bulk_density = + matric_density * (1.0f64 - gravel_volume_fraction) + gravel_volume_fraction * 2.65f64; + let bulk_plant_available_water = plant_available_water * (1.0f64 - gravel_volume_fraction); + let bulk_saturated_conductivity = saturated_conductivity * (1.0f64 - gravel_volume_fraction) + / (1.0f64 - gravel_volume_fraction * (1.0f64 - 3.0f64 * density_ratio / 2.0f64)); + Saxton2006GravelResult { + gravel_volume_fraction, + bulk_density, + bulk_plant_available_water, + bulk_saturated_conductivity, + } +} +#[cfg(test)] +mod calc_ptf_saxton2006_gravel_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn table_3_sand_twenty_percent_gravel() { + let result = calc_ptf_saxton2006_gravel( + 0.2f64, + 1.4264356197312436f64, + 0.05260734364858702f64, + 108.1478278507403f64, + ); + assert_close( + result.gravel_volume_fraction, + 0.11860834455314037f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.bulk_density, + 1.5715605653291098f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.bulk_plant_available_water, + 0.04636767370708995f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.bulk_saturated_conductivity, + 97.54880510583706f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +} +#[doc = r"Results returned by `calc_ptf_saxton2006_salinity`."] +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Saxton2006SalinityResult { + #[doc = r"Osmotic potential at saturation. (kPa)"] + pub saturated_osmotic_potential: f64, + #[doc = r"Osmotic potential at the supplied water content. (kPa)"] + pub osmotic_potential: f64, +} +#[doc = r"Estimate saturated and moisture-adjusted osmotic potential. + +# Arguments + + * electrical_conductivity: Electrical conductivity of a saturated soil extract. (dS/m) + * theta: Current volumetric water content. (m^3/m^3) + * theta_s: Saturated volumetric water content. (m^3/m^3) + +# Returns + +A [`Saxton2006SalinityResult`]. + +# Models + + * h(theta): Matric-plus-osmotic tension adjustment + +# Notes + +Prediction target: Osmotic potential from saturated-extract electrical conductivity. +The moisture adjustment assumes dissolved salt quantity remains near constant as water content +falls. + +# Warnings + +Precipitation, bonding, ionic nutrition, and toxicity can modify actual salinity effects."] +#[must_use] +pub fn calc_ptf_saxton2006_salinity( + electrical_conductivity: f64, + theta: f64, + theta_s: f64, +) -> Saxton2006SalinityResult { + let saturated_osmotic_potential = 36.0f64 * electrical_conductivity; + let osmotic_potential = theta_s / theta * saturated_osmotic_potential; + Saxton2006SalinityResult { + saturated_osmotic_potential, + osmotic_potential, + } +} +#[cfg(test)] +mod calc_ptf_saxton2006_salinity_tests { + use super::*; + fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64) { + assert!( + (actual - expected).abs() <= atol + rtol * expected.abs(), + "actual {actual} != expected {expected}" + ); + } + #[test] + fn saline_partly_saturated_soil() { + let result = calc_ptf_saxton2006_salinity(4f64, 0.3f64, 0.46172240764858724f64); + assert_close( + result.saturated_osmotic_potential, + 144f64, + 0.000000000001f64, + 0.000000000001f64, + ); + assert_close( + result.osmotic_potential, + 221.6267556713219f64, + 0.000000000001f64, + 0.000000000001f64, + ); + } +}