From 61d605c74c1f68d93ff3145e0cef258ff2117cab Mon Sep 17 00:00:00 2001
From: Daniel Vedder <daniel.vedder@idiv.de>
Date: Thu, 24 Nov 2022 17:27:05 +0100
Subject: [PATCH] Implemented config file reading and commandline argument
 parsing

Borrowed quite a bit of code from GeMM here, that made it easier...
---
 Manifest.toml          |  2 +-
 Project.toml           |  3 ++
 README.md              |  5 ++-
 src/Persephone.jl      | 19 +++++----
 src/core/input.jl      | 93 ++++++++++++++++++++++++++++++++++++++++++
 src/core/simulation.jl | 10 +++--
 6 files changed, 118 insertions(+), 14 deletions(-)

diff --git a/Manifest.toml b/Manifest.toml
index 6125adc..a0f42ca 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -2,7 +2,7 @@
 
 julia_version = "1.8.2"
 manifest_format = "2.0"
-project_hash = "3c3cc3b9de5c42b80fd17ce2f0cc11a00abed995"
+project_hash = "72816161d7cf94aaf2f40b7992f0ee1d99e390ce"
 
 [[deps.Agents]]
 deps = ["CSV", "DataFrames", "DataStructures", "Distributed", "Downloads", "Graphs", "JLD2", "LazyArtifacts", "LightOSM", "LinearAlgebra", "Pkg", "ProgressMeter", "Random", "Requires", "Scratch", "StatsBase"]
diff --git a/Project.toml b/Project.toml
index 1eb36b7..fe8960a 100644
--- a/Project.toml
+++ b/Project.toml
@@ -6,4 +6,7 @@ version = "0.0.1"
 [deps]
 Agents = "46ada45e-f475-11e8-01d0-f70cc89e6671"
 ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
+Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
 Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
+Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
diff --git a/README.md b/README.md
index 9e45a83..60b723f 100644
--- a/README.md
+++ b/README.md
@@ -16,10 +16,11 @@ It is currently in the very early stages of development.
 Install the latest version of the [Julia](https://julialang.org/downloads/) programming 
 language. The recommended editors are [VSCode](https://www.julia-vscode.org/) or
 [Emacs](https://www.emacswiki.org/emacs/JuliaProgrammingLanguage) (see `docs/editors.md`).
+To install package dependencies, open a Julia REPL and run 
+`using Pkg; Pkg.activate("."); Pkg.instantiate()`.
 
 Run the simulation by executing `run.jl` from the commandline, or loading it from
-within a Julia REPL with `include("run.jl")`. All dependencies should be
-automatically installed.
+within a Julia REPL with `include("run.jl")`. 
 
 This model uses the [Agents.jl](https://juliadynamics.github.io/Agents.jl/stable/) framework.
 
diff --git a/src/Persephone.jl b/src/Persephone.jl
index 7200d20..3be2462 100644
--- a/src/Persephone.jl
+++ b/src/Persephone.jl
@@ -15,23 +15,28 @@ module Persephone
 using
     Agents,
     ArgParse,
-    Logging
+    Dates,
+    Logging,
+    Random,
+    TOML
 
 ## define exported functions and variables
 export
-    simulate
+    simulate,
+    initsim,
+    runsim
+
+## The file that stores all default parameters
+const paramfile = "src/parameters.toml"
+## (DO NOT CHANGE THIS VALUE! Instead, specify simulation-specific configuration files
+## by using the "--configfile" commandline argument, or when invoking simulate().) 
 
 ## include all module files
 include("core/input.jl")
-
 include("core/output.jl")
-
 include("crop/crops.jl")
-
 include("ecology/ecology.jl")
-
 include("farm/farm.jl")
-
 include("core/simulation.jl")
 
 end
diff --git a/src/core/input.jl b/src/core/input.jl
index e69de29..9d147fc 100644
--- a/src/core/input.jl
+++ b/src/core/input.jl
@@ -0,0 +1,93 @@
+### Persephone - a socio-economic-ecological model of European agricultural landscapes.
+###
+### This file includes functions for configuring the model and reading in map files.
+###
+
+## Note: some of this code was adapted from the GeMM model
+## (https://github.com/CCTB-Ecomods/gemm/blob/master/src/input.jl)
+
+"""
+    getsettings(configfile)
+
+Combines all configuration options to produce a single settings dict.
+Precedence: commandline parameters - user config file - default values
+"""
+function getsettings(configfile::String)
+    # read in and merge configurations from the commandline, the default config file
+    # and a user-supplied config file
+    defaults = TOML.parsefile(configfile)
+    commandline = parsecommandline()
+    if haskey(commandline, "configfile") && isfile(commandline["configfile"])
+        configs = TOML.parsefile(commandline["configfile"])
+    else
+        configs = nothing
+    end
+    settings = deepcopy(defaults)
+    for domain in keys(defaults)
+        for param in keys(defaults[domain])
+            if param in keys(commandline)
+                settings[domain][param] = commandline[param]
+            elseif !isnothing(configs) && param in keys(configs[domain])
+                settings[domain][param] = configs[domain][param]
+            end
+        end
+    end
+    # pre-process certain parameters
+    if settings["core"]["seed"] == 0
+        settings["core"]["seed"] = abs(rand(RandomDevice(), Int32))
+    end
+    defaultoutdir = defaults["core"]["outdir"]
+    if settings["core"]["outdir"] == defaultoutdir
+        outdir = defaultoutdir*"_"*string(Dates.today())*"_s"*string(settings["core"]["seed"])
+        settings["core"]["outdir"] = outdir
+    end
+    # Flags are automatically set to false by ArgParse if they are not given -
+    # this should not override a given config file value
+    if !commandline["quietmode"] && !isnothing(configs) && "quietmode" in keys(configs["core"]) && configs["core"]["quietmode"]
+        settings["core"]["quietmode"] = true
+    end
+    settings
+end
+
+"""
+    parsecommandline()
+
+Certain software parameters can be set via the commandline.
+"""
+function parsecommandline()
+    s = ArgParseSettings()
+    @add_arg_table! s begin
+        "--configfile", "-c"
+            help = "name of the config file"
+            arg_type = String
+            required = false
+        "--seed", "-s"
+            help = "inital random seed"
+            arg_type = Int
+        "--mapfile", "-m"
+            help = "name of the map file"
+            arg_type = String
+            required = false
+        "--outdir", "-o"
+            help = "location of the output directory"
+            arg_type = String
+            required = false
+        "--loglevel", "-l"
+            help = "amount of logging: \"debug\", \"normal\", or \"errors\""
+            arg_type = String
+            required = false
+        "--quietmode", "-q"
+            help = "quiet mode. Don't print output to screen."
+            action = :store_true
+        "--runtime", "-r"
+           help = "duration in days that the simulation will run"
+           arg_type = Int
+           required = false
+    end
+    args = parse_args(s)
+    for a in keys(args)
+        (args[a] == nothing) && delete!(args, a)
+    end
+    args
+end
+
diff --git a/src/core/simulation.jl b/src/core/simulation.jl
index 8445922..7ddda5e 100644
--- a/src/core/simulation.jl
+++ b/src/core/simulation.jl
@@ -3,7 +3,10 @@
 ### This file includes the core functions for initialising and running simulations.
 ###
 
-function initsim()
+function initsim(config::String)
+    settings = getsettings(config)
+    TOML.print(settings)
+    Random.seed!(settings["core"]["seed"])
     #TODO
 end
 
@@ -11,10 +14,9 @@ function runsim()
     #TODO
 end
 
-function simulate()
-    initsim()
+function simulate(config::String=paramfile)
+    initsim(config)
     runsim()
     println("Simulation ran. Nothing happened. But it will!")
-    println("Simulation complete :-)")
     #TODO
 end
-- 
GitLab