Skip to content

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.

double DegreeDays(double MinC, double MaxC, const CropParameters& P);

Example

DegreeDays(16, 28, Corn); // (28 + 16) / 2 - 10 = 12 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.

double Growth(double Gdd, const CropParameters& P);

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.

int Bits(uint16_t Mask);

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).

double Crush(CropState& S, uint16_t Mask);

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.

double Harvest(CropState& S, uint16_t Mask, double Gdd, const CropParameters& P, double CapacityKg, bool bTubers = false);

CropParameters#

struct 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#

struct 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#

struct 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#

struct 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#

class 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&lt;float> Bed elevation per cell, m.
Unit std::vector&lt;uint8_t> Index into Units per cell.
Cover std::vector&lt;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&lt;WaterUnit> Soil types referenced by Unit. Must be filled before Initialize().
Covers std::vector&lt;WaterCover> Cover classes referenced by Cover. Must be filled before Step().
Theta std::vector&lt;double> Surface-layer water content, m3/m3.
H std::vector&lt;double> Pond depth, m.
ThetaInitial std::vector&lt;double> Antecedent surface water content, m3/m3 (baseline for storage and Green-Ampt F).
Compaction std::vector&lt;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&lt;double> Subsoil water content, m3/m3.
Theta2Initial std::vector&lt;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&lt;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&lt;int> Fine tile slot per cell (-1 = no tile), sized by Initialize.
Fine std::vector&lt;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.

std::function<void(int, const std::function<void(int)>&)> Parallel;

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.
void Initialize(int Width, double Cell, const float* Heights, const uint8_t* Units8, const uint8_t* Covers8);

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.
void Step(double Dt, double RainMmH, double EvapMmDay);

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.

double Residual(int I) const;

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).

double TotalResidual() const;

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.

double Surface(int I) const ;

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.
void StampFine(int FX, int FY, double RutM, double CompactionValue);

WaterGrid::ClearFine#

Drop every fine tile (the runtime rebuilds them from its rut map after a load).

void ClearFine();

WaterGrid::PrepareFine#

Refresh Sorted, MeanCompaction and StorageM of every tile stamped since the last call.

void PrepareFine();

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.

double EffectiveCompaction(int I) const;

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.

double Detention(int I) const;

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).

double FineLevel(int Slot, double PondM);

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].
void FineAnomalies(int Slot, float* Out) const;

FineTile#

struct 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&lt;float> m Rut depth below the unrutted cell bed, m.
Compaction std::vector&lt;float> Wheel compaction 0..1 (the effective value is max(this, the parent's whole-cell Compaction)).
Anomaly std::vector&lt;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&lt;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.