Basic Run
For a very simple example, see the section Basic Run in Getting Started.
Intermediate Run
To simulate crops growing on an agricultural field, we must first set a runtype (which defines the input file format) and the data directory:
using AquaCrop
parentdir = AquaCrop.test_toml_dir #".../AquaCrop.jl/test/testcase/TOML_FILES"
runtype = TomlFileRun()
Next we can create an AquaCropField
object, which is the core struct that stores all parameters and variables for one simulation run. This is initialised using the function start_cropfield
:
cropfield, all_ok = start_cropfield(;runtype=runtype, parentdir=parentdir);
dump(all_ok)
# output
AquaCrop.AllOk
logi: Bool true
msg: String ""
Here, all_ok
tell us whether the parameters have been loaded correctly (if all_ok.logi
is true
). If there was a problem, the error type is specified in all_ok.msg
. (Note that we do not raise exceptions, so that the cropfield
variable can still be inspected, such as cropfield.outputs[:logger]
). In out example we see that all_ok.logi == true
, so all went well up to now. We can update the crop field one day at a time, this is done using the dailyupdate!
function:
ndays = 30
for _ in 1:ndays
dailyupdate!(cropfield)
end
isequal(size(cropfield.dayout), (ndays, 89))
# output
true
We can see the daily output DataFrame in the field cropfield.dayout
:
cropfield.dayout
# output
30×89 DataFrame
Row │ RunNr Date DAP Stage WC() Rain Irri Surf ⋯
│ Int64 Date Int64 Int64 Quantity… Quantity… Quantity… Quan ⋯
─────┼──────────────────────────────────────────────────────────────────────────
1 │ 1 2014-05-21 1 1 866.491 mm 0.1 mm 0.0 mm 0 ⋯
2 │ 1 2014-05-22 2 2 864.965 mm 1.9 mm 0.0 mm 0
3 │ 1 2014-05-23 3 2 864.28 mm 2.4 mm 0.0 mm 0
4 │ 1 2014-05-24 4 2 862.077 mm 1.2 mm 0.0 mm 0
5 │ 1 2014-05-25 5 2 859.819 mm 1.3 mm 0.0 mm 0 ⋯
6 │ 1 2014-05-26 6 2 858.465 mm 1.2 mm 0.0 mm 0
7 │ 1 2014-05-27 7 2 857.867 mm 2.4 mm 0.0 mm 0
8 │ 1 2014-05-28 8 2 856.889 mm 0.4 mm 0.0 mm 0
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱
24 │ 1 2014-06-13 24 2 884.881 mm 17.4 mm 0.0 mm 0 ⋯
25 │ 1 2014-06-14 25 2 874.395 mm 3.8 mm 0.0 mm 0
26 │ 1 2014-06-15 26 2 866.108 mm 0.1 mm 0.0 mm 0
27 │ 1 2014-06-16 27 2 862.106 mm 0.2 mm 0.0 mm 0
28 │ 1 2014-06-17 28 2 868.995 mm 11.0 mm 0.0 mm 0 ⋯
29 │ 1 2014-06-18 29 2 868.812 mm 4.1 mm 0.0 mm 0
30 │ 1 2014-06-19 30 2 863.843 mm 0.2 mm 0.0 mm 0
82 columns and 15 rows omitted
If we want to know the biomass of the current day we use biomass
function:
biomass(cropfield)
# output
1.9197377309729786 ton ha⁻¹
Note that the result is in ton/ha
, metric tons per hectare. The amount of dry yield of the current day is given by the dryyield
function:
dryyield(cropfield)
# output
1.7616862576301124 ton ha⁻¹
And the fresh yield by the freshyield
function:
freshyield(cropfield)
# output
8.808431288150562 ton ha⁻¹
The canopy cover is given by the percentage of terrain covered by the crop, for this use the function canopycover
:
canopycover(cropfield)
# output
49.26884621144958
If we want to initiate a harvest on the current day, use harvest!
:
harvest!(cropfield)
cropfield.harvestsout
# output
2×11 DataFrame
Row │ RunNr Nr Date DAP Interval Biomass Sum(B) ⋯
│ Int64 Int64 Date Int64 Quantity… Quantity… Quantity… ⋯
─────┼──────────────────────────────────────────────────────────────────────────
1 │ 1 0 2014-05-21 0 0.0 d 0.0 ton ha⁻¹ 0.0 t ⋯
2 │ 1 1 2014-06-20 31 31.0 d 2.03624 ton ha⁻¹ 2.03624 t
5 columns omitted
Note that calling harvest!
also includes a daily update, now we have ndays+1
days
isequal(size(cropfield.dayout), (ndays+1, 89))
# output
true
Another effect of the harvest!
function is to change the biomass (obviously):
biomass(cropfield) # biomass is zero after a harvest day
# output
0.0 ton ha⁻¹
It also changes the canopy cover, we can see the value of it just before harvesting:
canopycover(cropfield) # canopy cover just before harvesting
# output
49.9040070260628
...and just after harvesting:
canopycover(cropfield, actual=false) # canopy cover just after harvesting
# output
25.0
(Harvesting is done at the end of the day, that is why we have two values of canopy cover.) If we do another daily update we see that the canopy cover is updated:
dailyupdate!(cropfield)
canopycover(cropfield)
# output
27.827808455159015
Since it was not a harvesting day it does not matter if we set actual=false
canopycover(cropfield, actual=false)
# output
27.827808455159015
Finally we can run until the end of the season using season_run!
:
season_run!(cropfield)
isequal(size(cropfield.dayout), (164, 89))
# output
true
And check the output dataframe of the season using:
cropfield.seasonout
# output
1×36 DataFrame
Row │ RunNr Date1 Rain ETo GD CO2 Irri ⋯
│ Int64 Date Quantity… Quantity… Float64 Quantity… Quantity… ⋯
─────┼──────────────────────────────────────────────────────────────────────────
1 │ 1 2014-05-21 487.5 mm 431.0 mm 1802.6 398.82 ppm 0.0 mm ⋯
29 columns omitted
Advanced Run
Beyond simply setting up and running a simulation, the AquaCrop.jl
API gives you more fine-grained control over what is going on. This allows one to:
- Have more control of state variables,
- Use input data stored in memory (faster, as it avoids disk I/O),
- Integrate with other Julia libraries and software.
Let's start by calling some libraries and creating some variables:
using AquaCrop
using DataFrames
using Dates
using StableRNGs
rng = StableRNG(42)
# Auxiliar variables to generate ficticius climate data
start_date = Date(2023, 1, 1) # January 1 2023
end_date = Date(2023, 6, 1) # June 1 2023
tmin = 15 # daily minimum temperature will be around this
delta_t = 10 # daily maximum temperature will be around tmin + delta_t
eto = 1 # daily ETo will be around this
rain = 1 # daily rain will be around this
Now we will create a function to mockup some climate DataFrame:
# Function to create a mockup climate DataFrame
function create_mock_climate_dataframe(start_date::Date, end_date::Date, tmin, delta_t, eto, rain)
# Generate the date range
dates = collect(start_date:end_date)
# Generate random climate columns (each column has the same number of rows as the date range)
Tmin = tmin .+ rand(rng, length(dates))
Tmax = Tmin .+ delta_t .+ rand(rng, length(dates))
ETo = eto .* abs.(randn(rng, length(dates)))
Rain = rain .* abs.(randn(rng, length(dates)))
# Create the DataFrame
df = DataFrame(
Date = dates,
Tmin = Tmin,
Tmax = Tmax,
ETo = ETo,
Rain = Rain
)
return df
end
df = create_mock_climate_dataframe(start_date, end_date, tmin, delta_t, eto, rain)
# output
152×5 DataFrame
Row │ Date Tmin Tmax ETo Rain
│ Date Float64 Float64 Float64 Float64
─────┼────────────────────────────────────────────────────
1 │ 2023-01-01 15.5805 25.7028 0.651892 2.91776
2 │ 2023-01-02 15.1912 25.8489 1.27642 0.633148
3 │ 2023-01-03 15.9711 26.3123 0.815404 1.05268
4 │ 2023-01-04 15.7434 26.2699 1.28974 0.205817
5 │ 2023-01-05 15.171 25.5827 0.21546 0.0733287
6 │ 2023-01-06 15.7048 26.1394 0.0967601 1.52228
7 │ 2023-01-07 15.441 26.284 0.238206 0.446309
8 │ 2023-01-08 15.804 26.2381 0.43073 0.539408
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮
146 │ 2023-05-26 15.3848 26.1798 0.857995 0.433004
147 │ 2023-05-27 15.6592 26.1444 1.41443 2.38353
148 │ 2023-05-28 15.5007 26.292 0.226759 0.429369
149 │ 2023-05-29 15.6813 26.624 0.220586 0.88746
150 │ 2023-05-30 15.8692 26.2971 0.142944 0.235242
151 │ 2023-05-31 15.6776 26.629 0.331443 0.851964
152 │ 2023-06-01 15.9877 26.9083 0.160163 1.67827
137 rows omitted
This advanced run depends on sending all the configuration via a keyword variable:
# Generate the keyword object for the simulation
kwargs = (
## Necessary keywords
# runtype
runtype = NoFileRun(),
# project input
Simulation_DayNr1 = start_date,
Simulation_DayNrN = end_date,
Crop_Day1 = start_date + Week(1),
Crop_DayN = end_date,
# soil
soil_type = "clay",
# crop
crop_type = "maize",
# Climate
InitialClimDate = start_date,
## Optional keyworkds
# Climate
Tmin = df.Tmin,
Tmax = df.Tmax,
ETo = df.ETo,
Rain = df.Rain,
# change soil properties
soil_layers = Dict("Thickness" => 5.0)
)
nothing # ignore this line
The variable kwargs
has some necessary keywords, like the runtype, where we specify that we will not use files to make the configuration (see NoFileRun
), and some optional keywords to pass the climate data, like temperature Tmin = df.Tmin
, or additional information to change some properties of the variables, like soil_layers = Dict("Thickness" => 5.0)
. To know more about the keywords, see AquaCrop.check_nofilerun
.
Once we have created the kwargs
we can start a crop field:
# start cropfield
cropfield, all_ok = start_cropfield(; kwargs...)
dump(all_ok)
# output
AquaCrop.AllOk
logi: Bool true
msg: String ""
and the climate is loaded:
cropfield.raindatasim
# output
152-element Vector{Float64}:
2.9177565288611698
0.6331479661044
1.0526800752671774
0.20581692403731217
0.07332874480489955
1.5222784325808019
0.44630933889682284
0.5394084077226324
0.4667865106229535
0.9548987102476532
⋮
0.09979662306558626
0.8290059898452288
0.43300412658464604
2.383533709338484
0.429368530738495
0.8874604322470359
0.23524192561168275
0.8519635419668649
1.6782700226385654
Now that we have finished starting the cropfield
, we can update the cropfield one day at a time, this is done using the dailyupdate!
function:
# daily update cropfield
ndays = 30
for _ in 1:ndays
dailyupdate!(cropfield)
end
isequal(size(cropfield.dayout), (ndays, 89))
# output
true
We can ask wheter a crop is harvestable or not with the isharvestable
function:
logi = isharvestable(cropfield)
# output
false
and the amount of days until is harvestable with timetoharvest
:
th = timetoharvest(cropfield)
# output
56
So, we run until our crop is harvestable:
for i in 1:th
dailyupdate!(cropfield)
end
logi = isharvestable(cropfield)
# output
true
Similarly to the Intermediate Run tutorial, if we want to conduct a harvest on the current day, we use harvest!
:
# harvest cropfield
harvest!(cropfield)
cropfield.harvestsout
# output
2×11 DataFrame
Row │ RunNr Nr Date DAP Interval Biomass Sum(B) ⋯
│ Int64 Int64 Date Int64 Quantity… Quantity… Quantity… ⋯
─────┼──────────────────────────────────────────────────────────────────────────
1 │ 1 0 2023-01-08 0 0.0 d 0.0 ton ha⁻¹ 0.0 ton ⋯
2 │ 1 1 2023-03-28 80 1.0 d 2.3493 ton ha⁻¹ 2.3493 ton
5 columns omitted
Note: we can use the harvest!
function even if isharvestable(cropfield) = false
We can also change the climate data during the course of a run. First we create a new mockup climate with the current simulation date of the cropfield
:
# change climate data
daynri_now = cropfield.gvars[:integer_parameters][:daynri]
day_now, month_now, year_now = AquaCrop.determine_date(daynri_now)
date_now = Date(year_now, month_now, day_now)
df_new = create_mock_climate_dataframe(date_now, end_date, tmin, delta_t, eto, rain)
# output
65×5 DataFrame
Row │ Date Tmin Tmax ETo Rain
│ Date Float64 Float64 Float64 Float64
─────┼─────────────────────────────────────────────────────
1 │ 2023-03-29 15.6869 26.6467 0.768085 1.10762
2 │ 2023-03-30 15.9483 26.269 0.83741 0.958011
3 │ 2023-03-31 15.2689 25.3166 0.621509 0.118426
4 │ 2023-04-01 15.7582 26.5199 0.481912 0.423281
5 │ 2023-04-02 15.4462 25.9803 0.204325 0.285356
6 │ 2023-04-03 15.1402 25.5491 0.0294312 0.725632
7 │ 2023-04-04 15.0549 25.2621 0.247941 1.23419
8 │ 2023-04-05 15.715 25.837 1.29638 1.37908
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮
59 │ 2023-05-26 15.204 25.9407 0.183608 0.157345
60 │ 2023-05-27 15.5289 26.4718 0.455157 2.31394
61 │ 2023-05-28 15.4316 25.5648 1.76768 1.02026
62 │ 2023-05-29 15.7017 26.0601 0.964421 1.09466
63 │ 2023-05-30 15.9557 26.7914 0.320716 0.609482
64 │ 2023-05-31 15.9225 26.3544 1.73078 0.00585092
65 │ 2023-06-01 15.1585 25.8627 2.5861 0.725034
50 rows omitted
and change the climate using change_climate_data!
function:
change_climate_data!(cropfield, df_new; kwargs...)
isapprox(cropfield.gvars[:float_parameters][:rain], df_new.Rain[1])
# output
true
Finally we can run until the end of the season using season_run!
function:
season_run!(cropfield)
total_days = length(collect(start_date:end_date))
isequal(size(cropfield.dayout), (total_days, 89))
# output
true