Logger
The Parquet state logger (cropforge/logger.py) serialises the full simulation state at every timestep.
Output Location
Parquet data is written to:
Where session_name is <farm_name>_<UTC_timestamp>, e.g. WheatBasic_20260626T183000.
Table Schemas
The logger writes three partitioned Parquet tables:
plants/
One row per plant per day per field.
| Column | Type | Description |
|---|---|---|
day |
int32 | Simulation day. |
field_name |
string | Field identifier. |
plant_id |
string | e.g. "r00c03". |
row, col |
int16 | Grid position. |
age_days |
int16 | Days since sowing. |
lai |
float32 | Leaf Area Index. |
biomass_g |
float32 | Dry biomass (g). |
height_cm |
float32 | Canopy height (cm). |
root_depth_cm |
float32 | Root depth (cm). |
stress_index |
float32 | Cumulative stress (0–1). |
alive |
bool | Plant viability. |
phenological_stage |
string | Growth stage label. |
custom_json |
string | JSON-serialised PlantState.custom dict. |
soil/
One row per soil voxel (row × col × layer) per day per field.
| Column | Type | Description |
|---|---|---|
day |
int32 | Simulation day. |
field_name |
string | Field identifier. |
row, col |
int16 | Grid position. |
layer |
int8 | Layer index. |
depth_top_cm, depth_bottom_cm |
float32 | Layer boundaries (cm). |
moisture_pct |
float32 | Volumetric water content (%). |
nitrogen_kg_ha |
float32 | Available N (kg ha⁻¹). |
bulk_density |
float32 | Bulk density (g cm⁻³). |
penetration_resistance |
float32 | Soil strength (MPa). |
custom_json |
string | JSON-serialised custom dict. |
environment/
One row per field per day.
| Column | Type | Description |
|---|---|---|
day |
int32 | Simulation day. |
field_name |
string | Field identifier. |
doy |
int16 | Day of year. |
temp_max_c, temp_min_c, temp_mean_c |
float32 | Temperature (°C). |
radiation_mj_m2 |
float32 | Solar radiation (MJ m⁻²). |
rainfall_mm |
float32 | Rainfall (mm). |
et0_mm |
float32 | Reference ET (mm). |
wind_speed_ms |
float32 | Wind speed (m s⁻¹). |
humidity_pct |
float32 | Relative humidity (%). |
co2_ppm |
float32 | CO₂ concentration (ppm). |
events_fired |
string | JSON array of event names that fired this day. |
custom_json |
string | JSON-serialised custom dict. |
Reading Logs Externally
import pandas as pd
plants = pd.read_parquet("cropforge_output/MyFarm_20260626T120000/plants/")
soil = pd.read_parquet("cropforge_output/MyFarm_20260626T120000/soil/")
env = pd.read_parquet("cropforge_output/MyFarm_20260626T120000/environment/")
# Filter to one field and one day
day45_plotA = plants[
(plants["field_name"] == "Plot_A") & (plants["day"] == 45)
]
Partitioning by field_name and day means queries on a single day/field are very fast — PyArrow only reads the relevant partition files.