AcresFarmModel.h#
Acres/Source/Acres/AcresFarmModel.h Generated
Crop and soil-water maths for the farm (pure C++, no Unreal objects, no wall-clock time).
Everything here is SI: metres, seconds, kilograms, volumetric water content theta in m3/m3, temperatures in degrees Celsius.
The water model is WaterGrid: a 2D grid of two-layer soil columns (4 m cells) with Green-Ampt infiltration, drainage, tile drains, evaporation and diffusive-wave overland flow, plus a sparse 0.25 m wheel sub-grid (WaterGrid::FineTile) in the cells that wheels have crossed. AcresFarmRuntime.cpp owns one on a dedicated worker thread.
The crop part (CropParameters, DegreeDays, Growth, CropState, Crush, Harvest) is used directly by AcresFarmRuntime.cpp. Nothing in this file locks; callers serialise access.
DegreeDays#
Growing degree days for one day, "modified" (Purdue/NDSU) method.
GDD = max(0, (min(MaxC, CapC) + max(MinC, BaseC)) / 2 - BaseC). Only the maximum is capped and only the minimum is floored, so a night warmer than CapC is not capped (rare in practice).
| Argument | Description |
|---|---|
MinC |
Daily minimum air temperature, deg C. |
MaxC |
Daily maximum air temperature, deg C. |
P |
Crop parameters (BaseC, CapC). |
Returns: Degree days accumulated over a full day, deg C day.
Growth#
Growth stage as a linear fraction of thermal time.
| Argument | Description |
|---|---|
Gdd |
Accumulated growing degree days, deg C day. |
P |
Crop parameters (MaturityGddC). |
Returns: Gdd / MaturityGddC clamped to [0, 1]; 1 means mature and harvestable.
Bits#
Number of set bits in a 16-bit mask (population count).
| Argument | Description |
|---|---|
Mask |
Sample mask. |
Returns: Count of set bits, 0 to 16.
Crush#
Mark samples as crushed.
Samples already crushed or harvested are skipped, so repeated passes do not double count.
| Argument | Description |
|---|---|
S |
Patch state, updated in place. |
Mask |
Samples under the tyre footprint. |
Returns: Newly crushed area, m2 (new bits * 1.52 / 16).
Harvest#
Harvest samples into the bunker.
Only a mature crop (Growth >= 1) can be harvested. Each untouched sample under Mask yields YieldKgM2 * 1.52 / 16 kg. Samples are taken in bit order until the next one would overflow CapacityKg; the rest stay standing.
| Argument | Description |
|---|---|
S |
Patch state, updated in place. |
Mask |
Samples under the header. |
Gdd |
Accumulated degree days of the field, deg C day. |
P |
Crop parameters of the patch's crop. |
CapacityKg |
Free space left in the bunker, kg. |
bTubers |
Root crop (potato): the tubers are underground, so samples a wheel crushed can still be dug. |
Returns: Crop collected, kg. 0 when immature or the bunker is full.
CropParameters#
Thermal-time crop parameters, loaded from the "crops" block of farm.json (one set for corn, one for soybean).
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
BaseC |
double |
°C | 10 |
Base temperature below which the crop does not develop, deg C. |
CapC |
double |
°C | 30 |
Upper cutoff for the daily maximum, deg C. |
MaturityGddC |
double |
°C | 1400 |
Growing degree days to reach maturity, deg C day. |
YieldKgM2 |
double |
1.1 |
Harvestable yield of a mature, undamaged crop, kg/m2. |
CropState#
Damage state of one authored crop patch (1.52 m x 1 m, about 0.095 m2 per sample).
Each patch is sampled on a 4 x 4 grid. Bit (J * 4 + I) is sample column I (along world X, 0.38 m apart) and row J (along world Y, 0.25 m apart). Crushed and harvested bits never overlap and stay set until the field is replanted.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
Crushed |
uint16_t |
0 |
Samples flattened by a wheel. | |
Harvested |
uint16_t |
0 |
Samples taken by the cutter. 16-bit masks. |
WaterUnit#
Soil hydraulic properties for one water-grid soil type (a SSURGO map unit, a tiled copy of one, pavement, gravel or a per-field override from the menu).
The column has two layers: a surface layer that wheels feel (rain, evaporation and sealing act here) over a subsoil store that drains at a rate set by the NRCS drainage class. Water contents are m3/m3.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
KsMps, SealedKsMps, SuctionM, Saturated, FieldCapacity, Wilting, DepthM |
double |
KsMps: saturated conductivity of the unsealed surface, m/s. SealedKsMps: infiltration ceiling of a crusted surface, m/s (applies to bare and row-crop cells when sealing is on; 1e9 means "no limit"). SuctionM: Green-Ampt wetting-front suction psi, m. Saturated, FieldCapacity, Wilting: surface-layer water contents at saturation, -33 kPa and -1500 kPa. DepthM: surface-layer thickness, m. | ||
Saturated2, FieldCapacity2, Wilting2, DepthM2 |
double |
Saturated2, FieldCapacity2, Wilting2: subsoil water contents, m3/m3. DepthM2: subsoil thickness, m. | ||
TileMps |
double |
m/s | 0 |
Tile-drain capacity out of the subsoil, m/s (0 = no tiles). |
VgN |
double |
N | 1.6 |
Van Genuchten n for the surface-to-subsoil flux. |
Residual |
double |
.06 |
Residual water content, m3/m3. | |
PercolationCapMps |
double |
m/s | 1 |
Deep-drainage ceiling out of the bottom of the subsoil, m/s. Set per NRCS drainage class: poorly drained units sit on slowly permeable till or a seasonal water table that the column cannot represent otherwise. |
Pavement |
bool |
false |
True for asphalt and concrete: no infiltration, no soil storage, theta stays 0. |
WaterCover#
Surface roughness of one land-cover class, used by overland flow.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
ManningN |
double |
N | .05 |
Manning roughness for sheet flow, s/m^(1/3) (SCS TR-55 Table 3-1). |
DetentionM |
double |
m | .002 |
Micro-relief storage, m; water shallower than this does not flow. |
WaterGrid#
Terrain-routed surface water over a square grid of two-layer soil columns.
Each cell holds a surface layer (Theta), a subsoil (Theta2), a pond depth H and cumulative ledgers for the mass balance. Step() first does the vertical water budget per cell (rain, Green-Ampt infiltration, surface to subsoil redistribution, deep and tile drainage, evaporation) over the whole step, then routes ponded water between 4-neighbours with an explicit diffusive-wave scheme in CFL-limited substeps. Ponds emerge from the terrain: depressions fill and spill; there is no fixed pond capacity.
Cells that wheels have crossed also carry a fine tile (FineTile, 0.25 m sub-cells, allocated on demand): rut depth and compaction per sub-cell, a zero-mean moisture anomaly, and a water-filling split of the pond into the ruts. The parent sees the tile only through its area-weighted compaction (Ks) and its rut storage (extra detention); the ledgers below stay per 4 m cell and close exactly as before.
The runtime sets the public configuration fields, fills Units and Covers, calls Initialize() once and then Step() in 10 s environment-time chunks from its worker thread. Cells are indexed I = Y * W + X.
Note
Not thread safe. The only internal parallelism is the optional Parallel executor used inside Route().
Example
AcresFarm::WaterGrid G;
G.Units = {AcresFarm::WaterUnit{}};
G.Covers = {AcresFarm::WaterCover{}};
G.Initialize(41, 4, Heights.data(), UnitIds.data(), CoverIds.data());
for (int I = 0; I < 360; ++I)
G.Step(10, 60, 2); // one hour of 60 mm/h rain
const double Error = G.TotalResidual();
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
W |
int |
0 |
Grid width and height in cells (the grid is square). | |
CellM |
double |
m | 4 |
Cell size, m. |
KsScale |
double |
1 |
Multiplier on every Ks (explores crusting or tillage). | |
CompactionKsLoss |
double |
.7 |
Fraction of Ks lost at full wheel compaction, applied after the sealing cap. | |
CflFactor |
double |
.8 |
Courant number for the routing substep. | |
MaxVelocityMps |
double |
m/s | 1.5 |
Cap on sheet-flow velocity, m/s. |
MinSubstepS |
double |
s | 1 |
Shortest routing substep, s. |
EdgeSlope |
double |
-1 |
Grid-edge boundary; < 0 means closed (no outflow), >= 0 means free outflow with the terrain assumed to continue at the interior slope, floored at this value (m/m). | |
Z |
std::vector<float> |
Bed elevation per cell, m. | ||
Unit |
std::vector<uint8_t> |
Index into Units per cell. | ||
Cover |
std::vector<uint8_t> |
Index into Covers per cell (0 bare, 1 corn, 2 soybean, 3 grass, 4 woodland, 5 shrubs, 6 asphalt, 7 concrete, 8 gravel, 9 potato in the runtime; see AcresCrops.h). | ||
Units |
std::vector<WaterUnit> |
Soil types referenced by Unit. Must be filled before Initialize(). | ||
Covers |
std::vector<WaterCover> |
Cover classes referenced by Cover. Must be filled before Step(). | ||
Theta |
std::vector<double> |
Surface-layer water content, m3/m3. | ||
H |
std::vector<double> |
Pond depth, m. | ||
ThetaInitial |
std::vector<double> |
Antecedent surface water content, m3/m3 (baseline for storage and Green-Ampt F). | ||
Compaction |
std::vector<double> |
Whole-cell wheel compaction 0..1 (only set by saves from before the fine layer or when the tile budget is used up; wheel compaction normally lives in the fine tile, see EffectiveCompaction). | ||
Theta2 |
std::vector<double> |
Subsoil water content, m3/m3. | ||
Theta2Initial |
std::vector<double> |
Antecedent subsoil water content, m3/m3. | ||
SurfaceSealing |
bool |
true |
When true, bare and row-crop cells (cover codes 0-2 and 9) infiltrate at most at the unit's SealedKsMps (raindrop crusting). Grass and woodland keep the unsealed rate. | |
RainM, InfiltratedM, EvapM, DrainM, TileM, LateralM, RunoffM |
std::vector<double> |
Cumulative per-cell ledgers, m of water: rain, infiltration, evaporation (pond plus soil), deep drainage, tile drainage, net lateral inflow from neighbours, and runoff that left through the open grid edge. | ||
ClockS |
double |
s | 0 |
Environment time simulated so far, s. |
Substeps |
double |
0 |
Total routing substeps taken (a counter stored as double). | |
MaxVelocity |
double |
0 |
Largest sheet-flow velocity seen during the last Step(), m/s. | |
FineDiv |
int |
16 |
Sub-cells per side of a fine tile (16 gives 0.25 m on 4 m cells). | |
MaxFineTiles |
int |
16384 |
Tile budget; once it is used up, stamps on further cells fall back to whole-cell compaction (the pre-fine behaviour). | |
FineRelaxS |
double |
s | 86400 |
e-folding time of the fine moisture anomaly, s: lateral capillary redistribution in the surface layer evens out a wet rut bottom against its ridges over about a day (L^2 / D with L ~ 0.3 m and soil water diffusivity D ~ 1e-6 m2/s). The anomaly decays toward 0; the parent's Theta is unaffected. |
FineSlot |
std::vector<int> |
Fine tile slot per cell (-1 = no tile), sized by Initialize. | ||
Fine |
std::vector<FineTile> |
Allocated fine tiles, in allocation order. |
WaterGrid::void#
Optional parallel executor: must call Body(ChunkIndex) for every ChunkIndex in [0, Chunks), in any order and on any threads, and return when all are done. Empty means serial.
WaterGrid::Initialize#
Size the grid and set every cell to its unit's field capacity (both layers, pavement 0).
Units must already be filled. The runtime overwrites Theta and Theta2 (and their Initial copies) straight after this call with its own antecedent moisture.
| Argument | Description |
|---|---|
Width |
Cells per side. |
Cell |
Cell size, m. |
Heights |
Width * Width bed elevations, m. |
Units8 |
Width * Width indices into Units. |
Covers8 |
Width * Width indices into Covers. |
WaterGrid::Step#
Advance the whole grid by one environment step.
Vertical budget once over Dt, then routing in substeps of CflFactor * CellM / v (clamped to [min(MinSubstepS, Dt), min(Dt, 10 s)]) until Dt is used up. Only cells ponded above their detention depth at the start of the step are routed.
| Argument | Description |
|---|---|
Dt |
Step, s. Nothing happens if Dt <= 0. Steps shorter than MinSubstepS route in one substep. |
RainMmH |
Rain rate, mm/h, uniform over the grid. |
EvapMmDay |
Potential evaporation, mm/day, uniform over the grid. |
WaterGrid::Residual#
Mass-balance error of one cell.
| Argument | Description |
|---|---|
I |
Cell index, Y * W + X. |
Returns: Rain + net lateral inflow - pond - storage change (both layers) - evaporation - drainage - tile - edge runoff, m. Should stay near 0.
WaterGrid::TotalResidual#
Sum of Residual() over all cells, m. Lateral terms cancel between neighbours.
Returns: Total mass-balance error, m (summed per-cell depths, not a volume).
WaterGrid::Surface#
Water-surface elevation of a cell, m (bed plus pond).
| Argument | Description |
|---|---|
I |
Cell index, Y * W + X. |
Returns: Z[I] + H[I], m.
WaterGrid::StampFine#
Record one wheel stamp on a fine sub-cell. Values only ever increase (max with what is stored).
Allocates the parent's fine tile on first use (rut 0, compaction 0, anomaly 0). When MaxFineTiles tiles already exist, the stamp's compaction goes to the parent's whole-cell Compaction instead (and the rut is dropped), which is how the grid behaved before the fine layer. Stamps outside the grid are ignored. Aggregates are refreshed lazily by PrepareFine() (Step() calls it).
| Argument | Description |
|---|---|
FX |
Fine column, 0 .. W * FineDiv - 1 (sub-cell size CellM / FineDiv, from the grid corner). |
FY |
Fine row, same range. |
RutM |
Rut depth below the unrutted bed, m. |
CompactionValue |
Wheel compaction 0..1. |
WaterGrid::ClearFine#
Drop every fine tile (the runtime rebuilds them from its rut map after a load).
WaterGrid::PrepareFine#
Refresh Sorted, MeanCompaction and StorageM of every tile stamped since the last call.
WaterGrid::EffectiveCompaction#
Compaction the parent's infiltration uses: the tile's area-weighted MeanCompaction when the cell has a fine tile (PrepareFine() must have run since the last stamp), else the whole-cell Compaction.
| Argument | Description |
|---|---|
I |
Cell index, Y * W + X. |
Returns: Compaction 0..1.
WaterGrid::Detention#
Depth below which ponded water in a cell does not flow: the cover's micro-relief detention plus the rut storage of its fine tile.
| Argument | Description |
|---|---|
I |
Cell index, Y * W + X. |
Returns: Detention depth, m of water over the cell.
WaterGrid::FineLevel#
Water level of a tile relative to the unrutted cell bed, for pond depth PondM spread over its sub-cells.
Water fills the deepest ruts first: the local depth of sub-cell K is max(0, Level + RutM[K]), and the mean of the local depths over the tile equals PondM exactly, so the split creates or destroys no water. With no pond the level is minus the deepest rut (every sub-cell dry). Refreshes the tile if it is dirty.
| Argument | Description |
|---|---|
Slot |
Index into Fine. |
PondM |
Cell pond depth to distribute, m (normally H[Fine[Slot].Cell]). |
Returns: Level, m (negative while the water stays inside the ruts).
WaterGrid::FineAnomalies#
Moisture anomaly of every sub-cell of a tile at the current ClockS (the stored values with the FineRelaxS decay since RelaxedS applied).
| Argument | Description |
|---|---|
Slot |
Index into Fine. |
Out |
Receives FineDiv * FineDiv values, m3/m3. Local theta = Theta[Cell] + Out[K]. |
FineTile#
Wheel sub-grid ("fine layer") of one 4 m cell: FineDiv x FineDiv sub-cells (0.25 m at the defaults), allocated the first time a wheel stamps the cell.
A tyre contact patch is 0.5-0.7 m wide, so a 4 m cell cannot say whether the tyre is in a rut or on the ridge beside it. The fine layer adds that detail without changing what the routed 4 m hydrology conserves: every quantity here either feeds the parent as an area-weighted mean (compaction, rut storage) or is a zero-sum split of the parent's water (the pond level and the moisture anomaly). Sub-cell index K = J * FineDiv + I, with I along +X and J along +Y inside the parent cell.
| Name | Type | Unit | Default | Description |
|---|---|---|---|---|
Cell |
int |
-1 |
Parent cell index, Y * W + X. | |
RutM |
std::vector<float> |
m | Rut depth below the unrutted cell bed, m. | |
Compaction |
std::vector<float> |
Wheel compaction 0..1 (the effective value is max(this, the parent's whole-cell Compaction)). | ||
Anomaly |
std::vector<float> |
Surface-layer water content minus the parent's Theta, m3/m3; zero mean over the tile, referred to environment time RelaxedS. | ||
Sorted |
std::vector<float> |
RutM sorted deepest first (for the pond water-filling), valid while !Dirty. | ||
MeanCompaction |
double |
0 |
Area-weighted effective compaction, what the parent's infiltration uses. | |
StorageM |
double |
m | 0 |
Mean rut depth, m (rut volume per unit cell area; ponded water below it cannot flow). |
RelaxedS |
double |
s | 0 |
Environment time the stored Anomaly values refer to, s. |
Dirty |
bool |
true |
True after a stamp until PrepareFine() refreshes Sorted, MeanCompaction and StorageM. |