Skip to content
Sindbad.Setup Module

Setup

The Setup module provides tools for setting up and configuring SINDBAD experiments and runs. It handles the creation of experiment configurations, model structures, parameters, and output setups, ensuring a streamlined workflow for SINDBAD simulations.

Purpose

This module is designed to produce the SINDBAD info object, which contains all the necessary configurations and metadata for running SINDBAD experiments. It facilitates reading configurations, building model structures, and preparing outputs.

Dependencies

Related (SINDBAD ecosystem)

  • ErrorMetrics: Metric type construction for cost options.

  • TimeSamplers: Temporal setup helpers.

  • OmniTools: Shared utilities (e.g. Table).

External (third-party)

  • CSV, JLD2, JSON: Configuration and persistence tooling.

  • ConstructionBase, Dates, NaNStatistics: Core utilities used during setup.

  • Infiltrator: Optional interactive debugging (reexported for convenience).

Internal (within Sindbad)

  • Sindbad.Types

  • SindbadTEM

Included Files

  • defaultOptions.jl: Default configuration knobs for optimization, sensitivity, and machine-learning routines referenced during setup.

  • utilsSetup.jl: Shared setup helpers (validation, convenience utilities).

  • generateCode.jl: Code-generation helpers used by development workflows.

  • getConfiguration.jl: Read JSON/CSV configuration and normalize into internal settings representation.

  • setupSimulationInfo.jl: Build the simulation info NamedTuple that downstream modules consume.

  • setupTypes.jl: Instantiate SINDBAD types after parsing settings (time, land, metrics, optimization, etc.).

  • setupPools.jl: Initialize land pools and state variables based on model structure/helpers/constants.

  • updateParameters.jl: Apply metric feedback to parameter values (e.g., during optimization iterations).

  • setupParameters.jl: Load parameter metadata (bounds/timescales/priors) and prepare arrays used by optimization/ML.

  • setupModels.jl: Validate/order selected processes, wiring approaches to the overall run sequence.

  • setupOutput.jl: Configure diagnostic/output arrays, filenames, and write schedules.

  • setupParameterOptimization.jl: Collect optimizer-specific settings (algorithm, stopping criteria, restarts, etc.).

  • setupHybridMachineLearning.jl: Hybrid ML configuration (fold definitions, feature sets, surrogate wiring).

  • setupInfo.jl: Final assembly step integrating all previous pieces into the info object exported to simulations.

Notes

  • The package re-exports several key packages (Infiltrator, CSV, JLD2) for convenience, allowing users to access their functionality directly through Setup.

  • Designed to be modular and extensible, enabling users to customize and expand the setup process for specific use cases.

Functions

backScaleParameters

Sindbad.Setup.backScaleParameters Function
julia
backScaleParameters(parameter_vector_scaled, parameter_table, <: ParameterScaling)

Reverts scaling of parameters using a specified scaling strategy.

Arguments

  • parameter_vector_scaled: Vector of scaled parameters to be converted back to original scale

  • parameter_table: Table containing parameter information and scaling factors

  • ParameterScaling: Type indicating the scaling strategy to be used

    • ::ScaleDefault: Type indicating scaling by initial parameter values

    • ::ScaleBounds: Type indicating scaling by parameter bounds

    • ::ScaleNone: Type indicating no scaling should be applied (parameters remain unchanged)

Returns

Returns the unscaled/actual parameter vector in original units.

Code
julia
function backScaleParameters end

function backScaleParameters(parameter_vector_scaled, parameter_table, ::ScaleNone)
    return parameter_vector_scaled
end

function backScaleParameters(parameter_vector_scaled, parameter_table, ::ScaleNone)
    return parameter_vector_scaled
end

function backScaleParameters(parameter_vector_scaled, parameter_table, ::ScaleDefault)
    parameter_vector_scaled = abs.(parameter_table.initial) .* parameter_vector_scaled
    return parameter_vector_scaled
end

function backScaleParameters(parameter_vector_scaled, parameter_table, ::ScaleBounds)
    ub = parameter_table.upper  # upper bounds
    lb = parameter_table.lower   # lower bounds
    parameter_vector_scaled .= lb + (ub - lb) .* parameter_vector_scaled
    return parameter_vector_scaled
end

checkParameterBounds

Sindbad.Setup.checkParameterBounds Function
julia
checkParameterBounds(p_names, parameter_values, lower_bounds, upper_bounds, _sc::ParameterScaling; show_info=false, model_names=nothing)

Check and display the parameter bounds information for given parameters.

Arguments

  • p_names: Names or identifier of the parameters. Vector of strings.

  • parameter_values: Default values of the parameters. Vector of Numbers.

  • lower_bounds: Lower bounds for the parameters. Vector of Numbers.

  • upper_bounds: Upper bounds for the parameters. Vector of Numbers.

  • _sc::ParameterScaling: Scaling Type for the parameters

  • show_info: a flag to display model parameters and their bounds. Boolean.

  • model_names: Names or identifier of the approaches where the parameters are defined.

Returns

Displays a formatted output of parameter bounds information or returns an error when they are violated

Code
julia
function checkParameterBounds(p_names, parameter_values, lower_bounds, upper_bounds, _sc::ParameterScaling; p_units=nothing, show_info=false, model_names=nothing)
    if show_info
        print_info(checkParameterBounds, @__FILE__, @__LINE__, "checking Parameter Bounds")
        if nameof(typeof(_sc)) == :ScaleNone
            print_info(nothing, @__FILE__, @__LINE__, "→→→    no scaling applied. The values and bounds are original/input values, while their units, when provided, are scaled to match the model run time steps and may differ from the original units in the model when @timescale of the parameter is different from the model run time step.")
        else
            print_info(nothing, @__FILE__, @__LINE__, "→→→    $(nameof(typeof(_sc))) scaling applied. The values and bounds are scaled values, while their units, when provided, are scaled to match the model run time steps and may differ from the original units in the model when @timescale of the parameter is different from the model run time step. Check info.models.parameter_table for interpreting parameter values in original/input units.")
        end
    end
    for (i,n) in enumerate(p_names)
        in_range = checkInRange(n, parameter_values[i], lower_bounds[i], upper_bounds[i], show_info)
        if !in_range
            error("$(String(n)) => value=$(parameter_values[i]) [lower_bound=$(lower_bounds[i]), upper_bound=$(upper_bounds[i])] violates the parameter bounds requirement (lower_bound <= value <= upper_bound). Fix the bounds in the given model ($(model_names[i])) or in the parameters input to continue.")
        end
        if show_info
            ps = String(n)
            if !isnothing(model_names)
                ps = "`$(String(model_names[i]))`.jl: `$(String(n))`" 
            end
            units_str = ""
            if !isnothing(p_units)
                units_str = p_units[i] == "" ? "unitless" : "$(p_units[i])"
                units_str = "(units: $(units_str))"
            end
            print_info(nothing, @__FILE__, @__LINE__, "$(ps) => $(parameter_values[i]) [$(lower_bounds[i]), $(upper_bounds[i])] $units_str", n_f=6)

        end
    end
end

convertRunFlagsToTypes

Sindbad.Setup.convertRunFlagsToTypes Function
julia
convertRunFlagsToTypes(info)

Converts model run-related flags from the experiment configuration into types for dispatch.

Arguments:

  • info: A NamedTuple containing the experiment configuration, including model run flags.

Returns:

  • A NamedTuple new_run where each flag is converted into a corresponding type instance.

Notes:

  • Flags are processed recursively:

    • If a flag is a NamedTuple, its subfields are converted into types.

    • If a flag is a scalar, it is directly converted into a type using getTypeInstanceForFlags.

  • The resulting new_run NamedTuple is used for type-based dispatch in SINDBAD's model execution.

Examples

julia
julia> using Sindbad

julia> # Convert run flags to types
julia> # new_run = convertRunFlagsToTypes(info)
Code
julia
function convertRunFlagsToTypes(info)
    new_run = (;)
    dr = deepcopy(info.settings.experiment.flags)
    for pr in propertynames(dr)
        prf = getfield(dr, pr)
        prtoset = nothing
        if isa(prf, NamedTuple)
            st = (;)
            for prs in propertynames(prf)
                prsf = getfield(prf, prs)
                st = set_namedtuple_field(st, (prs, getTypeInstanceForFlags(prs, prsf)))
            end
            prtoset = st
        else
            prtoset = getTypeInstanceForFlags(pr, prf)
        end
        new_run = set_namedtuple_field(new_run, (pr, prtoset))
    end
    return new_run
end

createArrayofType

Sindbad.Setup.createArrayofType Function
julia
createArrayofType(input_values, pool_array, num_type, indx, ismain, array_type::ModelArrayType)

Creates an array or view of the specified type array_type based on the input values and configuration.

Arguments:

  • input_values: The input data to be converted or used for creating the array.

  • pool_array: A preallocated array from which a view may be created.

  • num_type: The numerical type to which the input values should be converted (e.g., Float64, Int).

  • indx: A tuple of indices used to create a view from the pool_array.

  • ismain: A boolean flag indicating whether the main array should be created (true) or a view should be created (false).

  • array_type: A type dispatch that determines the array type to be created:

    • ModelArrayView: Creates a view of the pool_array based on the indices indx.

    • ModelArrayArray: Creates a new array by converting input_values to the specified num_type.

    • ModelArrayStaticArray: Creates a static array (SVector) from the input_values.

Returns:

  • An array or view of the specified type, created based on the input configuration.

Notes:

  • When ismain is true, the function converts input_values to the specified num_type.

  • When ismain is false, the function creates a view of the pool_array using the indices indx.

  • For ModelArrayStaticArray, the function ensures that the resulting static array (SVector) has the correct type and length.

Examples

julia
julia> using Sindbad

julia> # Create a view from a preallocated array
julia> # pool_array = rand(10, 10)
julia> # indx = (1:5,)
julia> # view_array = createArrayofType(nothing, pool_array, Float64, indx, false, ModelArrayView())

julia> # Create a new array with a specific numerical type
julia> # new_array = createArrayofType([1.0, 2.0, 3.0], nothing, Float64, nothing, true, ModelArrayArray())

julia> # Create a static array (SVector)
julia> # static_array = createArrayofType([1.0, 2.0, 3.0], nothing, Float64, nothing, true, ModelArrayStaticArray())
Code
julia
function createArrayofType end

function createArrayofType(input_values, pool_array, num_type, indx, ismain, ::ModelArrayView)
    if ismain
        num_type.(input_values)
    else
        @view pool_array[[indx...]]
    end
end

function createArrayofType(input_values, pool_array, num_type, indx, ismain, ::ModelArrayView)
    if ismain
        num_type.(input_values)
    else
        @view pool_array[[indx...]]
    end
end

function createArrayofType(input_values, pool_array, num_type, indx, ismain, ::ModelArrayArray)
    return num_type.(input_values)
end

function createArrayofType(input_values, pool_array, num_type, indx, ismain, ::ModelArrayStaticArray)
    input_typed = typeof(num_type(1.0)) === eltype(input_values) ? input_values : num_type.(input_values) 
    return SVector{length(input_values)}(input_typed)
    # return SVector{length(input_values)}(num_type(ix) for ix ∈ input_values)
end

createInitLand

Sindbad.Setup.createInitLand Function
julia
createInitLand(pool_info, tem)

Initializes the land state by creating a NamedTuple with pools, states, and selected models.

Arguments:

  • pool_info: Information about the pools to initialize.

  • tem: A helper NamedTuple with necessary objects for pools and numbers.

Returns:

  • A NamedTuple containing initialized pools, states, fluxes, diagnostics, properties, models, and constants.

Examples

julia
julia> using Sindbad

julia> # Initialize land state for an experiment
julia> # init_land = createInitLand(pool_info, tem)
Code
julia
function createInitLand(pool_info, tem)
    print_info(createInitLand, @__FILE__, @__LINE__, "creating Initial Land...")
    init_pools = createInitPools(pool_info, tem.helpers)
    initial_states = createInitStates(pool_info, tem.helpers)
    out = (; fluxes=(;), pools=(; init_pools..., initial_states...), states=(;), diagnostics=(;), properties=(;), models=(;), constants=(;))
    sortedModels = sort([_sm for _sm  tem.models.selected_models.model])
    for model  sortedModels
        out = set_namedtuple_field(out, (model, (;)))
    end
    return out
end

createInitPools

Sindbad.Setup.createInitPools Function
julia
createInitPools(info_pools::NamedTuple, tem_helpers::NamedTuple)

Creates a NamedTuple with initial pool variables as subfields, used in land.pools.

Arguments:

  • info_pools: A NamedTuple containing pool information from the experiment configuration.

  • tem_helpers: A NamedTuple containing helper information for numerical operations.

Returns:

  • A NamedTuple with initialized pool variables.
Code
julia
function createInitPools(info_pools::NamedTuple, tem_helpers::NamedTuple)
    init_pools = (;)
    for element  propertynames(info_pools)
        props = getfield(info_pools, element)
        model_array_type = getfield(Types, to_uppercase_first(string(getfield(props, :arraytype)), "ModelArray"))()
        var_to_create = getfield(props, :create)
        initial_values = getfield(props, :initial_values)
        for tocr  var_to_create
            input_values = deepcopy(getfield(initial_values, tocr))
            init_pools = set_namedtuple_field(init_pools, (tocr, createArrayofType(input_values, Nothing[], tem_helpers.numbers.num_type, nothing, true, model_array_type)))
        end
        to_combine = getfield(getfield(info_pools, element), :combine)
        if to_combine.docombine
            combined_pool_name = to_combine.pool
            zix_pool = getfield(props, :zix)
            components = keys(zix_pool)
            pool_array = getfield(init_pools, combined_pool_name)
            for component  components
                if component != combined_pool_name
                    indx = getfield(zix_pool, component)
                    input_values = deepcopy(getfield(initial_values, component))
                    compdat = createArrayofType(input_values, pool_array, tem_helpers.numbers.num_type, indx, false, model_array_type)
                    init_pools = set_namedtuple_field(init_pools, (component, compdat))
                end
            end
        end
    end
    return init_pools
end

createInitStates

Sindbad.Setup.createInitStates Function
julia
createInitStates(info_pools::NamedTuple, tem_helpers::NamedTuple)

Creates a NamedTuple with initial state variables as subfields, used in land.states.

Arguments:

  • info_pools: A NamedTuple containing pool information from the experiment configuration.

  • tem_helpers: A NamedTuple containing helper information for numerical operations.

Returns:

  • A NamedTuple with initialized state variables.

Notes:

  • Extended from `createInitPools``

  • State variables are derived from the state_variables field in model_structure.json.

Code
julia
function createInitStates(info_pools::NamedTuple, tem_helpers::NamedTuple)
    initial_states = (;)
    for element  propertynames(info_pools)
        props = getfield(info_pools, element)
        var_to_create = getfield(props, :create)
        additional_state_vars = (;)
        if hasproperty(props, :state_variables)
            additional_state_vars = getfield(props, :state_variables)
        end
        initial_values = getfield(props, :initial_values)
        model_array_type = getfield(Types, to_uppercase_first(string(getfield(props, :arraytype)), "ModelArray"))()
        for tocr  var_to_create
            for avk  keys(additional_state_vars)
                avv = getproperty(additional_state_vars, avk)
                Δtocr = Symbol(string(avk) * string(tocr))
                vals = one.(getfield(initial_values, tocr)) *                                 tem_helpers.numbers.num_type(avv)
                newvals = createArrayofType(vals, Nothing[], tem_helpers.numbers.num_type, nothing, true, model_array_type)
                initial_states = set_namedtuple_field(initial_states, (Δtocr, newvals))
            end
        end
        to_combine = getfield(getfield(info_pools, element), :combine)
        if to_combine.docombine
            combined_pool_name = Symbol(to_combine.pool)
            for avk  keys(additional_state_vars)
                avv = getproperty(additional_state_vars, avk)
                Δ_combined_pool_name = Symbol(string(avk) * string(combined_pool_name))
                zix_pool = getfield(props, :zix)
                components = keys(zix_pool)
                Δ_pool_array = getfield(initial_states, Δ_combined_pool_name)
                for component  components
                    if component != combined_pool_name
                        Δ_component = Symbol(string(avk) * string(component))
                        indx = getfield(zix_pool, component)
                        Δ_compdat = createArrayofType((one.(getfield(initial_values, component))) .* tem_helpers.numbers.num_type(avv), Δ_pool_array, tem_helpers.numbers.num_type, indx, false, model_array_type)
                        initial_states = set_namedtuple_field(initial_states, (Δ_component, Δ_compdat))
                    end
                end
            end
        end
    end
    return initial_states
end

createNestedDict

Sindbad.Setup.createNestedDict Function
julia
createNestedDict(dict::AbstractDict)

Creates a nested dictionary from a flat dictionary where keys are strings separated by dots (.).

Arguments:

  • dict::AbstractDict: A flat dictionary with keys as dot-separated strings.

Returns:

  • A nested dictionary where each dot-separated key is converted into nested dictionaries.

Examples

julia
julia> using Sindbad

julia> dict = Dict("a.b.c" => 2, "a.b.d" => 3)
Dict{String, Int64} with 2 entries:
  "a.b.c" => 2
  "a.b.d" => 3

julia> nested_dict = createNestedDict(dict)
Dict{Any, Any} with 1 entry:
  "a" => Dict{Any, Any}("b"=>Dict{Any, Any}("c"=>2, "d"=>3))
Code
julia
function createNestedDict(dict::AbstractDict)
    nested_dict = Dict()
    for kii  keys(dict)
        key_list = split(kii, ".")
        key_dict = Dict()
        for key_index  reverse(eachindex(key_list))
            subkey = key_list[key_index]
            if subkey == first(key_list)
                subkey_name = subkey
            else
                subkey_name = subkey * string(key_index)
            end
            if subkey == last(key_list)
                key_dict[subkey_name] = dict[kii]
            else
                if !haskey(key_dict, subkey_name)
                    key_dict[subkey_name] = Dict()
                    key_dict[subkey_name][key_list[key_index+1]] = key_dict[key_list[key_index+1]*string(key_index+1)]
                else
                    tmp = Dict()
                    tmp[subkey_name] = key_dict[key_list[key_index+1]*string(key_index + 1)]
                end
                delete!(key_dict, key_list[key_index+1] * string(key_index + 1))
                delete!(nested_dict, key_list[key_index+1] * string(key_index + 1))
            end
            nested_dict = deepMerge(nested_dict, key_dict)
        end
    end
    return nested_dict
end

deepMerge

Sindbad.Setup.deepMerge Function
julia
deepMerge(d::AbstractDict...) = merge(deepMerge, d...)
deepMerge(d...) = d[end]

Recursively merges multiple dictionaries, giving priority to the last dictionary.

Arguments:

  • d::AbstractDict...: One or more dictionaries to merge.

Returns:

  • A single dictionary with merged fields, where the last dictionary's values take precedence.

Examples

julia
julia> using Sindbad

julia> dict1 = Dict("a" => Dict("b" => 1), "c" => 2)
Dict{String, Any} with 2 entries:
  "a" => Dict("b" => 1)
  "c" => 2

julia> dict2 = Dict("a" => Dict("d" => 3), "e" => 4)
Dict{String, Any} with 2 entries:
  "a" => Dict("d" => 3)
  "e" => 4

julia> merged = deepMerge(dict1, dict2)
Dict{String, Any} with 3 entries:
  "a" => Dict{String, Any}("b" => 1, "d" => 3)
  "c" => 2
  "e" => 4
Code
julia
function deepMerge end

deepMerge(d::AbstractDict...) = merge(deepMerge, d...)
deepMerge(d...) = d[end]

"""
    getRootDirs(local_root, sindbad_experiment)

Determines the root directories for the SINDBAD framework and the experiment.

# Arguments:
- `local_root`: The local root directory of the SINDBAD project.
- `sindbad_experiment`: The path to the experiment configuration file.

# Returns:
- A NamedTuple containing the root directories for the experiment, SINDBAD, and settings.
"""
function getRootDirs(local_root, sindbad_experiment)
    sindbad_root = join(split(local_root, path_separator)[1:(end-2)] |> collect, path_separator)
    exp_base_path = dirname(sindbad_experiment)
    root_dir = (; experiment=local_root, sindbad=sindbad_root, settings=exp_base_path)
    return root_dir
end

filterParameterTable

Sindbad.Setup.filterParameterTable Function
julia
filterParameterTable(parameter_table::Table; prop_name::Symbol=:model, prop_values::Tuple{Symbol}=(:all,))

Filters a parameter table based on a specified property and values.

Arguments

  • parameter_table::Table: The parameter table to filter

  • prop_name::Symbol: The property to filter by (default: :model)

  • prop_values::Tuple{Symbol}: The values to filter by (default: :all)

Returns

A filtered parameter table.

Examples

julia
julia> using Sindbad

julia> # Filter parameter table by model
julia> # filtered_table = filterParameterTable(parameter_table; prop_name=:model, prop_values=(:gpp,))

julia> # Return all parameters
julia> # all_params = filterParameterTable(parameter_table; prop_values=:all)
Code
julia
function filterParameterTable(parameter_table::Table; prop_name::Symbol=:model, prop_values::Union{Vector{Symbol},Symbol}=:all)
    if prop_values == :all
        return parameter_table
    elseif isa(prop_values, Symbol)
        return filter(row -> getproperty(row, prop_name) == prop_values, parameter_table)
    else
        return filter(row -> getproperty(row, prop_name) in prop_values, parameter_table)
    end
end

generateSindbadApproach

Sindbad.Setup.generateSindbadApproach Function
julia
generateSindbadApproach(model_name::Symbol, model_purpose::String, appr_name::Symbol, appr_purpose::String, n_parameters::Int; methods=(:define, :precompute, :compute, :update), force_over_write=:none)

Generate a SINDBAD model and/or approach with code templates.

Due to risk of overwriting code, the function only succeeds if y|Y||Yes|Ya, etc., are given in the confirmation prompt. This function only works if the call is copy-pasted into the REPL and not evaluated from a file/line. See the example below for the syntax.

Description

The generateSindbadApproach function creates a SINDBAD model and/or approach by generating code templates for their structure, parameters, methods, and documentation. It ensures consistency with the SINDBAD framework and adheres to naming conventions. If the model or approach already exists, it avoids overwriting existing files unless explicitly permitted. The generated code includes placeholders for methods (define, precompute, compute, update) and automatically generates docstrings for the model and approach.

Note that the newly created approaches are tracked by changes in tmp_precompile_placeholder.jl in the Sindbad root. The new models/approaches are automatically included ONLY when REPL is restarted.

Arguments

  • model_name: The name of the SINDBAD model to which the approach belongs.

  • model_purpose: A string describing the purpose of the model.

  • appr_name: The name of the approach to be generated.

  • appr_purpose: A string describing the purpose of the approach.

  • n_parameters: The number of parameters required by the approach.

  • methods: A tuple of method names to include in the approach (default: (:define, :precompute, :compute, :update)).

  • force_over_write: A symbol indicating whether to overwrite existing files or types. Options are:

    • :none (default): Do not overwrite existing files or types.

    • :model: Overwrite the model file and type.

    • :approach: Overwrite the approach file and type.

    • :both: Overwrite both model and approach files and types.

Returns

  • nothing: The function generates the required files and writes them to the appropriate directory.

Behavior

  • If the model does not exist, it generates a new model file with the specified model_name and model_purpose.

  • If the approach does not exist, it generates a new approach file with the specified appr_name, appr_purpose, and n_parameters.

  • Ensures that the approach name follows the SINDBAD naming convention (<model_name>_<approach_name>).

  • Prompts the user for confirmation before generating files to avoid accidental overwrites.

  • Includes placeholders for methods (define, precompute, compute, update) and generates a consistent docstring for the approach.

Example

julia
# Generate a new SINDBAD approach for an existing model

generateSindbadApproach(:ambientCO2, "Represents ambient CO2 concentration", :constant, "Sets ambient CO2 as a constant", 1)

# Generate a new SINDBAD model and approach

generateSindbadApproach(:newModel, "Represents a new SINDBAD model", :newApproach, "Implements a new approach for the model", 2)

# Generate a SINDBAD model and approach with force_over_write

generateSindbadApproach(:newModel, "Represents a new SINDBAD model", :newApproach, "Implements a new approach for the model", 2; force_over_write=:both) # overwrite both model and approach

generateSindbadApproach(:newModel, "Represents a new SINDBAD model", :newApproach, "Implements a new approach for the model", 2; force_over_write=:approach) # overwrite just approach approach

Notes

  • The function ensures that the generated code adheres to SINDBAD conventions and includes all necessary metadata and documentation.

  • If the model or approach already exists, the function does not overwrite the files unless explicitly confirmed by the user.

  • The function provides warnings and prompts to ensure safe file generation and minimize the risk of accidental overwrites.

Code
julia
function generateSindbadApproach(model_name::Symbol, model_purpose::String, appr_name::Symbol, appr_purpose::String, n_parameters::Int; methods=(:define, :precompute, :compute, :update), force_over_write=:none)
    was_model_created = false
    was_approach_created = false
    over_write_model = false
    over_write_appr = false
    if force_over_write == :none
        @info "Overwriting of type and file is off. Only new objects will be created"
    elseif force_over_write == :model
        over_write_model = true
        @warn "Overwriting of type and file for Model is permitted. Continue with care."
    elseif force_over_write == :approach
        over_write_appr = true
        @warn "Overwriting of type and file for Approach is permitted. Continue with care."
    elseif force_over_write == :both
        @warn "Overwriting of both type and files for Model and Approach are permitted. Continue with extreme care."
        over_write_model = true
        over_write_appr = true
    else
        error("force_over_write can only be one of (:none, :both, :model, :approach)")
    end    

    if !startswith(string(appr_name), string(model_name)*"_")
        @warn "the name $(appr_name) does not start with $(model_name), which is against the SINDBAD model component convention. Using $(model_name)_$(appr_name) as the name of the approach."
        appr_name = Symbol(string(model_name) *"_"* string(appr_name))
    end
    model_type_exists = model_name in nameof.(subtypes(LandEcosystem)) 
    model_path = joinpath(split(pathof(SindbadTEM),"/SindbadTEM.jl")[1], "Processes", "$(model_name)", "$(model_name).jl")
    model_path_exists = isfile(model_path)
    appr_path = joinpath(split(pathof(SindbadTEM),"/SindbadTEM.jl")[1], "Processes", "$(model_name)", "$(appr_name).jl")
    appr_path_exists = isfile(appr_path)

    model_path_exists = over_write_model ? false : model_path_exists

    model_exists = false
    if model_type_exists && model_path_exists
        @info "both model_path and model_type exist. No need to create the model."
        model_exists = true
    elseif model_type_exists && !model_path_exists
        @warn "model_type exists but (model_path does not exist || force_over_write is enabled with :$(force_over_write)). If force_over_write is not enabled, fix the inconsistency by moving the definition of th type to the file itself."
    elseif !model_type_exists && model_path_exists
        @warn "model_path exists but model_type does not exist. Fix this inconsistency by defining the type in the file."
        model_exists = true
    else
        @info "both model_type and (model_path do not exist || force_over_write is enabled with :$(force_over_write)). Model will be created."
    end

    if model_exists
        @info "Not generating model "
    else
        @info "Generating a new model: $(model_name) at:\n$(appr_path)"
        confirm_ = Base.prompt("Continue: y | n")
        if startswith(confirm_, "y")
            @info "Generating model code:"
            m_string=generateModelCode(model_name, model_purpose)
            mkpath(dirname(model_path))
            @info "Writing model code:"
            open(model_path, "w") do model_file
                write(model_file, m_string)
            end
            @info "success: $(model_path)"
            was_model_created = true
        end
    end

    appr_exists = false
    appr_type_exists = false
    if hasproperty(SindbadTEM.Processes, model_name)
        model_type = getproperty(SindbadTEM.Processes, model_name)
        appr_types = nameof.(subtypes(model_type))
        appr_type_exists = appr_name in appr_types
    end

    appr_path_exists = over_write_appr ? false : appr_path_exists

    if appr_type_exists && appr_path_exists
        @info "both appr_path and appr_type exist. No need to create the approach."
        appr_exists = true
    elseif appr_type_exists && !appr_path_exists
        @warn "appr_type exists but (appr_path does not exist || force_over_write is enabled with :$(force_over_write))). If force_over_write is not enabled, fix this inconsistency by defining the type in the file itself."
    elseif !appr_type_exists && appr_path_exists
        @warn "appr_path exists but appr_type does not exist. Fix this inconsistency by defining the type in the file."
    else
        @info "both appr_type and (appr_path do not exist || force_over_write is enabled with :$(force_over_write)). Approach will be created."
    end
    
    if appr_exists
        @info "Not generating approach."
    else
        appr_path = joinpath(split(pathof(SindbadTEM),"/SindbadTEM.jl")[1], "Processes", "$(model_name)", "$(appr_name).jl")
        @info "Generating a new approach: $(appr_name) for existing model: $(model_name) at:\n$(appr_path)"
        confirm_ = Base.prompt("Continue: y | n")
        if startswith(confirm_, "y")
            @info "Generating code:"
            appr_string = generateApproachCode(model_name, appr_name, appr_purpose, n_parameters; methods=methods)
            @info "Writing code:"
            open(appr_path, "w") do appr_file
                write(appr_file, appr_string)
            end
            @info "success: $(appr_path)"
            was_approach_created = true
        else
            @info "Not generating approach file due to user input."
        end
    end

    ## append the tmp_precompile_placeholder file so that Sindbad is forced to precompile in the next run_helpers
    if was_model_created || was_approach_created
        # Specify the file path
        file_path = joinpath(split(pathof(SindbadTEM),"/SindbadTEM.jl")[1], "tmp_precompile_placeholder.jl")

        # The line you want to add
        date = strip(read(`date +%d.%m.%Y`, String));

        new_lines = []
        if was_model_created
            new_line = "# - $(date): created a model $model_path.\n"
            push!(new_lines, new_line)
        end

        if was_approach_created
            new_line = "# - $(date): created an approach $appr_path.\n"
            push!(new_lines, new_line)
        end

        # Open the file in append mode
        open(file_path, "a") do file
            foreach(new_lines) do new_line
                write(file, new_line)
            end
        end  

    end
    return nothing
end

getAbsDataPath

Sindbad.Setup.getAbsDataPath Function
julia
getAbsDataPath(info, data_path)

Converts a relative data path to an absolute path based on the experiment directory.

Arguments:

  • info: The SINDBAD experiment information object.

  • data_path: The relative or absolute data path.

Returns:

An absolute data path.

Code
julia
function getAbsDataPath(info, data_path)
    if !isabspath(data_path)
        d_data_path = getSindbadDataDepot(local_data_depot=data_path)
        if data_path == d_data_path
            data_path = joinpath(info.experiment.dirs.experiment, data_path)
        else
            data_path = joinpath(d_data_path, data_path)
        end
    end
    return data_path
end

getConfiguration

Sindbad.Setup.getConfiguration Function
julia
getConfiguration(sindbad_experiment::String; replace_info=Dict())

Loads the experiment configuration from a JSON or JLD2 file.

Arguments:

  • sindbad_experiment::String: Path to the experiment configuration file.

  • replace_info::Dict: A dictionary of fields to replace in the configuration.

Returns:

  • A NamedTuple containing the experiment configuration.

Notes:

  • Supports both JSON and JLD2 formats.

  • If replace_info is provided, the specified fields are replaced in the configuration.

Examples

julia
julia> using Sindbad

julia> # Load configuration from JSON file
julia> # config = getConfiguration("experiment_config.json")

julia> # Load with configuration overrides
julia> # config = getConfiguration("experiment_config.json"; replace_info=Dict("output" => Dict("save_all" => true)))
Code
julia
function getConfiguration(sindbad_experiment::String; replace_info=Dict())
    local_root = dirname(Base.active_project())
    if !isabspath(sindbad_experiment)
        sindbad_experiment = joinpath(local_root, sindbad_experiment)
    end
    roots = getRootDirs(local_root, sindbad_experiment)
    roots = (; roots..., sindbad_experiment)
    info = nothing
    if endswith(sindbad_experiment, ".json")
        info_exp = getExperimentConfiguration(sindbad_experiment; replace_info=replace_info)
        info = readConfiguration(info_exp, roots.settings)
    elseif endswith(sindbad_experiment, ".jld2")
        #todo running from the jld2 file here still does not work because the loaded info is a named tuple and replacing the fields will not work due to issues with merge and creating a dictionary from nested namedtuple
        # info = Dict(pairs(load(sindbad_experiment)["info"]))
        info = load(sindbad_experiment)["info"]
    else
        error(
            "sindbad can only be run with either a json or a jld2 data file. Provide a correct experiment file"
        )
    end
    if !isempty(replace_info)
        non_exp_dict = filter(x -> !startswith(first(x), "experiment"), replace_info)
        if !isempty(non_exp_dict)
            info = replaceInfoFields(info, non_exp_dict)
        end
    end
    if !haskey(info["experiment"]["basics"]["config_files"], "optimization")
        @warn "The config files in experiment_json and changes in replace_info do not include optimization_json. But, the settings for flags to run_optimization [set as $(info["experiment"]["flags"]["run_optimization"])] and/or calc_cost [set as $(info["experiment"]["flags"]["calc_cost"])] are set to true. These flags will be set to false from now on. The experiment will not run as intended further downstream. Cannot run optimization or calculate cost without the optimization settings. "
        info["experiment"]["flags"]["run_optimization"] = false
        info["experiment"]["flags"]["calc_cost"] = false
    end

    new_info = DataStructures.OrderedDict()
    new_info["settings"] = DataStructures.OrderedDict()
    for (k,v) in info
        new_info["settings"][k] = v
    end
    # @show keys(info)
    if !endswith(sindbad_experiment, ".jld2")
        infoTuple = dict_to_namedtuple(new_info)
    end
    infoTuple = (; infoTuple..., temp=(; experiment=(; dirs=roots)))

    print_info_separator()

    return infoTuple
    # return info
end

getConstraintNames

Sindbad.Setup.getConstraintNames Function
julia
getConstraintNames(optim::NamedTuple)

Extracts observation and model variable names for optimization constraints.

Arguments:

  • optim: A NamedTuple containing optimization settings and observation constraints.

Returns:

  • A tuple containing:
    • obs_vars: A list of observation variables used to calculate cost.

    • optim_vars: A lookup mapping observation variables to model variables.

    • store_vars: A lookup of model variables for which time series will be stored.

    • model_vars: A list of model variable names.

Code
julia
function getConstraintNames(optim::NamedTuple)
    obs_vars = Symbol.(optim.observational_constraints)
    model_vars = String[]
    optim_vars = (;)
    for v  obs_vars
        vinfo = getproperty(optim.observations.variables, v)
        push!(model_vars, vinfo.model_full_var)
        vf, vvar = Symbol.(split(vinfo.model_full_var, "."))
        optim_vars = set_namedtuple_field(optim_vars, (v, tuple(vf, vvar)))
    end
    store_vars = getVariableGroups(model_vars)
    return obs_vars, optim_vars, store_vars, model_vars
end

getCostOptions

Sindbad.Setup.getCostOptions Function
julia
getCostOptions(optim_info::NamedTuple, vars_info, tem_variables, number_helpers, dates_helpers)

Sets up cost optimization options based on the provided parameters.

Arguments:

  • optim_info: A NamedTuple containing optimization parameters and settings.

  • vars_info: Information about variables used in optimization.

  • tem_variables: Template variables for optimization setup.

  • number_helpers: Helper functions or values for numerical operations.

  • dates_helpers: Helper functions or values for date-related operations.

Returns:

  • A NamedTuple containing cost optimization configuration options.

Notes:

  • Configures temporal and spatial aggregation, cost metrics, and other optimization-related settings.

Examples

julia
julia> using Sindbad

julia> # Setup cost options for optimization
julia> # cost_options = getCostOptions(optim_info, vars_info, tem_variables, number_helpers, dates_helpers)
Code
julia
function getCostOptions(optim_info::NamedTuple, vars_info, tem_variables, number_helpers, dates_helpers)
    varlist = Symbol.(optim_info.observational_constraints)
    agg_type = []
    time_aggrs = []
    aggr_funcs = []
    all_costs = map(varlist) do v
        merge_namedtuple_prefer_nonempty(optim_info.observations.default_cost, getfield(getfield(optim_info.observations.variables, v), :cost_options))
    end
    all_options = []
    push!(all_options, varlist)
    prop_names = keys(all_costs[1])
    props_to_keep = (:cost_metric, :spatial_weight, :cost_weight, :temporal_data_aggr, :aggr_obs, :aggr_order, :min_data_points, :spatial_data_aggr, :spatial_cost_aggr, :aggr_func,)
    for (pn, prop)  enumerate(props_to_keep)
        prop_array = []
        for vn  eachindex(varlist)
            sel_opt=all_costs[vn]
            sel_value = sel_opt[prop]
            if (sel_value isa Number) && !(sel_value isa Bool)
                sel_value = number_helpers.num_type(sel_value)
            elseif sel_value isa Bool
                sel_value=getTypeInstanceForFlags(prop, sel_value, "Do")
            elseif sel_value isa String && (prop == :cost_metric)
                sel_value = getTypeInstanceForCostMetric(ErrorMetrics, sel_value)
            elseif sel_value isa String && (prop  (:aggr_order, :spatial_data_aggr))
                sel_value = getTypeInstanceForCostMetric(Types, sel_value)
            end
            push!(prop_array, sel_value)
            if prop == :temporal_data_aggr
                t_a = sel_value
                to_push_type = TimeNoDiff()
                if endswith(lowercase(t_a), "_anomaly") || endswith(lowercase(t_a), "_iav")
                    to_push_type = TimeDiff()
                end
                push!(agg_type, to_push_type)
                push!(time_aggrs, sel_value)
            end
            if prop == :aggr_func
                push!(aggr_funcs, sel_value)
            end
        end
        if prop in props_to_keep
            push!(all_options, prop_array)
        end
    end
    mod_vars = vars_info.model
    mod_field = [Symbol(split(_a, ".")[1]) for _a in mod_vars]
    mod_subfield = [Symbol(split(_a, ".")[2]) for _a in mod_vars]
    mod_ind = collect(1:length(varlist))
    obs_sn = [i for i in mod_ind]
    obs_ind = [i + 3 * (i - 1) for i in mod_ind]

    mod_ind = [findfirst(s -> first(s) === mf && last(s) === msf, tem_variables) for (mf, msf) in zip(mod_field, mod_subfield)]
    # map(cost_option_table) do cost_option
    #     # @show cost_option
    #     new_mod_ind = findfirst(s -> first(s) === cost_option.mod_field && last(s) === cost_option.mod_subfield, tem_variables)
    #     cost_option = Accessors.@set cost_option.mod_ind = new_mod_ind
    # end

    agg_indices = []
    for (i, _aggr) in enumerate(time_aggrs)
        aggr_func = getAggrFunc(aggr_funcs[i])
        _aggr_name = string(_aggr)
        skip_sampling = false
        if startswith(_aggr_name, dates_helpers.temporal_resolution)
            skip_sampling = true
        end
        aggInd = create_TimeSampler(dates_helpers.range, to_uppercase_first(_aggr, "Time"), aggr_func, skip_sampling)
        push!(agg_indices, aggInd)
    end
    push!(all_options, obs_ind)
    push!(all_options, obs_sn)
    push!(all_options, mod_ind)
    push!(all_options, mod_field)
    push!(all_options, mod_subfield)
    push!(all_options, agg_indices)
    push!(all_options, agg_type)
    all_props = [:variable, props_to_keep..., :obs_ind, :obs_sn, :mod_ind, :mod_field, :mod_subfield, :temporal_aggr, :temporal_aggr_type]
    return (; Pair.(all_props, all_options)...)
end

getDepthDimensionSizeName

Sindbad.Setup.getDepthDimensionSizeName Function
julia
getDepthDimensionSizeName(vname::Symbol, info::NamedTuple)

Retrieves the name and size of the depth dimension for a given variable.

Arguments:

  • vname: The variable name.

  • info: A SINDBAD NamedTuple containing all information needed for setup and execution of an experiment.

Returns:

  • A tuple containing the size and name of the depth dimension.

Notes:

  • Validates the depth dimension against the depth_dimensions field in the experiment configuration.
Code
julia
function getDepthDimensionSizeName(v_full_pair, info::NamedTuple)
    v_full_str = getVariableString(v_full_pair)
    v_full_sym = Symbol(v_full_str)
    v_name = split(v_full_str, '.')[end]
    dim_size = 1
    dim_name = v_name * "_idx"
    tmp_vars = nothing
    tmp_vars = info.settings.experiment.model_output.variables
    if v_full_sym in keys(tmp_vars)
        v_dim = tmp_vars[v_full_sym]
        dim_size = 1
        dim_name = v_name * "_idx"
        if !isnothing(v_dim) && isa(v_dim, String)
            dim_name = v_dim
        end
        if isnothing(v_dim)
            dim_size = dim_size
        elseif isa(v_dim, Int64)
            dim_size = v_dim
        elseif isa(v_dim, String)
            if Symbol(v_dim) in keys(info.settings.experiment.model_output.depth_dimensions)
                dim_size_K = getfield(info.settings.experiment.model_output.depth_dimensions, Symbol(v_dim))
                if isa(dim_size_K, Int64)
                    dim_size = dim_size_K
                elseif isa(dim_size_K, String)
                    dim_size = getPoolSize(info.temp.helpers.pools, Symbol(dim_size_K))
                end
            else
                error(
                    "The output depth dimension for $(v_name) is specified as $(v_dim) but this key does not exist in depth_dimensions. Either add it to depth_dimensions or add a numeric value."
                )
            end
        else
            error(
                "The depth dimension for $(v_name) is specified as $(typeof(v_dim)). Only null, integers, or string keys to depth_dimensions are accepted."
            )
        end
    end
    return dim_size, dim_name
end

getDepthInfoAndVariables

Sindbad.Setup.getDepthInfoAndVariables Function
julia
getDepthInfoAndVariables(info, output_vars)

Generates depth information and variable pairs for the output variables.

Arguments:

  • info: A SINDBAD NamedTuple containing experiment configuration.

  • output_vars: A list of output variables.

Returns:

  • A NamedTuple containing depth information and variable pairs.
Code
julia
function getDepthInfoAndVariables(info, output_vars)
    out_vars_pairs = Tuple(getVariablePair.(output_vars))
    depth_info = map(out_vars_pairs) do vname_full
        getDepthDimensionSizeName(vname_full, info)
    end
    output_info=(; info.output..., depth_info, variables=out_vars_pairs)
    return output_info
end

getExperimentConfiguration

Sindbad.Setup.getExperimentConfiguration Function
julia
getExperimentConfiguration(experiment_json::String; replace_info=Dict())

Loads the basic configuration from an experiment JSON file.

Arguments:

  • experiment_json::String: Path to the experiment JSON file.

  • replace_info::Dict: A dictionary of fields to replace in the configuration.

Returns:

  • A dictionary containing the experiment configuration.
Code
julia
function getExperimentConfiguration(experiment_json::String; replace_info=Dict())
    parseFile = parsefile(experiment_json; dicttype=DataStructures.OrderedDict)
    info = DataStructures.OrderedDict()
    info["experiment"] = DataStructures.OrderedDict()
    for (k, v)  parseFile
        info["experiment"][k] = v
    end
    if !isempty(replace_info)
        exp_dict = filter(x -> startswith(first(x), "experiment"), replace_info)
        if !isempty(exp_dict)
            info = replaceInfoFields(info, exp_dict)
        end
    end
    return info
end

getExperimentInfo

Sindbad.Setup.getExperimentInfo Function
julia
getExperimentInfo(sindbad_experiment::String; replace_info=Dict())

Loads and sets up the experiment configuration, saving the information and enabling debugging options if specified.

Arguments:

  • sindbad_experiment::String: Path to the experiment configuration file.

  • replace_info::Dict: (Optional) A dictionary of fields to replace in the configuration.

Returns:

  • A NamedTuple info containing the fully loaded and configured experiment information.

Notes:

  • The function performs the following steps:
    1. Loads the experiment configuration using getConfiguration.

    2. Sets up the experiment info using setupInfo.

    3. Saves the experiment info if save_info is enabled.

    4. Sets up a debug error catcher if catch_model_errors is enabled.

Examples

julia
julia> using Sindbad

julia> # Load experiment configuration
julia> # info = getExperimentInfo("experiment_config.json")

julia> # Load with configuration overrides
julia> # info = getExperimentInfo("experiment_config.json"; replace_info=Dict("output" => Dict("save_all" => true)))
Code
julia
function getExperimentInfo(sindbad_experiment::String; replace_info=Dict())
    replace_info_text = isempty(replace_info) ? "none" : " $(Tuple(keys(replace_info)))"
    print_info_separator()
    
    print_info(getExperimentInfo, @__FILE__, @__LINE__, "loading experiment configurations", n_m=1)
    print_info(nothing, @__FILE__, @__LINE__, "→→→    experiment_path: `$(sindbad_experiment)`", n_m=1)

    print_info(nothing, @__FILE__, @__LINE__, "→→→    replace_info_fields: `$(replace_info_text)`", n_m=1)
    info = getConfiguration(sindbad_experiment; replace_info=deepcopy(replace_info))

    info = setupInfo(info)
    saveInfo(info, info.helpers.run.save_info)
    setDebugErrorCatcher(info.helpers.run.catch_model_errors)
    return info
end

getGlobalAttributesForOutCubes

Sindbad.Setup.getGlobalAttributesForOutCubes Function
julia
getGlobalAttributesForOutCubes(info)

Generates global attributes for output cubes, including system and experiment metadata.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • A dictionary global_attr containing global attributes such as:
    • simulation_by: The user running the simulation.

    • experiment: The name of the experiment.

    • domain: The domain of the experiment.

    • date: The current date.

    • machine: The machine architecture.

    • os: The operating system.

    • host: The hostname of the machine.

    • julia: The Julia version.

Notes:

  • The function collects system information using Julia's Sys module and versioninfo.
Code
julia
function getGlobalAttributesForOutCubes(info)
    os = Sys.iswindows() ? "Windows" : Sys.isapple() ? "macOS" : Sys.islinux() ? "Linux" : "unknown"
    simulation_by = Sys.iswindows() ? ENV["USERNAME"] : ENV["USER"]
    io = IOBuffer()
    versioninfo(io)
    str = String(take!(io))
    julia_info = split(str, "\n")

    # io = IOBuffer()
    # Pkg.status("Sindbad", io=io)
    # sindbad_version = String(take!(io))
    global_attr = Dict(
        "simulation_by" => simulation_by,
        "experiment" => info.temp.experiment.basics.name,
        "domain" => info.temp.experiment.basics.domain,
        "date" => string(Date(now())),
        # "SINDBAD" => sindbad_version,
        "machine" => Sys.MACHINE,
        "os" => os,
        "host" => gethostname(),
        "julia" => string(VERSION),
    )
    return global_attr
end

getNumberType

Sindbad.Setup.getNumberType Function
julia
getNumberType(t)

Retrieves the numerical type based on the input, which can be a string or a data type.

Arguments:

  • t: The input specifying the numerical type. Can be:
    • A String representing the type (e.g., "Float64", "Int").

    • A DataType directly specifying the type (e.g., Float64, Int).

Returns:

  • The corresponding numerical type as a DataType.

Notes:

  • If the input is a string, it is parsed and evaluated to return the corresponding type.

  • If the input is already a DataType, it is returned as-is.

Examples

julia
julia> using Sindbad

julia> getNumberType("Float64")
Float64

julia> getNumberType(Float64)
Float64

julia> getNumberType("Int")
Int64
Code
julia
function getNumberType end

function getNumberType(t::String)
    ttype = eval(Meta.parse(t))
    return ttype
end

function getNumberType(t::String)
    ttype = eval(Meta.parse(t))
    return ttype
end

function getNumberType(t::DataType)
    return t
end

getOptimizationParametersTable

Sindbad.Setup.getOptimizationParametersTable Function
julia
getOptimizationParametersTable(parameter_table_all::Table, model_parameter_default, optimization_parameters)

Creates a filtered and enhanced parameter table for optimization by combining input parameters with default model parameters with the table of all parameters in the selected model structure.

Arguments

  • parameter_table_all::Table: A table containing all model parameters

  • model_parameter_default: Default parameter settings including distribution and a flag differentiating if the parameter is to be ML-parameter-learnt

  • optimization_parameters: Parameters to be optimized, specified either as:

    • ::NamedTuple: Named tuple with parameter configurations

    • ::Vector: Vector of parameter names to use with default settings

Returns

A filtered Table containing only the optimization parameters, enhanced with:

  • is_ml: Boolean flag indicating if parameter uses machine learning

  • dist: Distribution type for each parameter

  • p_dist: Distribution parameters as an array of numeric values

Notes

  • Parameters can be specified using comma-separated strings for model.parameter pairs

  • For NamedTuple inputs, individual parameter configurations override model_parameter_default

  • The output table preserves the numeric type of the input parameters

Code
julia
function getOptimizationParametersTable(parameter_table_all::Table, model_parameter_default, optimization_parameters)
    parameter_list = []
    parameter_keys = []
    if isa(optimization_parameters, NamedTuple)
        parameter_keys = keys(optimization_parameters)
    else
        parameter_keys = optimization_parameters
    end
    parameter_list = replaceCommaSeparatedParams(parameter_keys)
    missing_parameters = filter(x -> !(x in parameter_table_all.name_full), parameter_list)
    if !isempty(missing_parameters)
        error("Model Inconsistency: $([missing_parameters...]) parameter(s) not found in the selected model structure. Check the model structure in model_structure.json to include the parameter(s) or change model_parameters_to_optimize in optimization.json to exclude the parameter(s).")
    end
    parameter_table_all_filtered = filter(row -> row.name_full in parameter_list, parameter_table_all)
    num_type = typeof(parameter_table_all_filtered.default[1])
    is_ml = parameter_table_all_filtered.is_ml
    dist = parameter_table_all_filtered.dist
    p_dist = parameter_table_all_filtered.p_dist
    for (p_ind, p_key)  enumerate(parameter_keys)
        p_field = nothing
        was_default_used = false
        if isa(optimization_parameters, NamedTuple)
            p_field = getproperty(optimization_parameters, p_key)
            if isnothing(p_field)
                p_field = model_parameter_default
                was_default_used = true
            end
        else 
            was_default_used = true
            p_field = model_parameter_default
        end
        if !isnothing(p_field)
            if hasproperty(p_field, :is_ml)
                ml_json = getproperty(p_field, :is_ml)
                is_ml[p_ind] = ml_json
            end
            if hasproperty(p_field, :distribution)
                nd_json = getproperty(p_field, :distribution)
                dist[p_ind] = nd_json[1]
                p_dist[p_ind] = [num_type.(nd_json[2])...]
            end
        end
    end
    return parameter_table_all_filtered
end

getParameterIndices

Missing docstring.

Missing docstring for getParameterIndices. Check Documenter's build log for details.

Code
julia
function getParameterIndices(selected_models::LongTuple, parameter_table::Table)
    selected_models_tuple = to_tuple(selected_models)
    return getParameterIndices(selected_models_tuple, parameter_table)
end

function getParameterIndices(selected_models::Tuple, parameter_table::Table)
    r = (;)
    tempvec = Pair{Symbol,Int}[]
    for m in selected_models
        r = (; r..., getModelParameterIndices(m, parameter_table, tempvec)...)
    end
    r
end

getParameters

Sindbad.Setup.getParameters Function
julia
getParameters(selected_models::Tuple, num_type, model_timestep; return_table=true)
getParameters(selected_models::LongTuple, num_type, model_timestep; return_table=true)

Retrieves parameters for the specified models with given numerical type and timestep settings.

Arguments

  • selected_models: A collection of selected models

    • ::Tuple: as a tuple

    • ::LongTuple: as a long tuple

  • num_type: The numerical type to be used for parameters

  • model_timestep: The timestep setting for the model simulation

  • return_table::Bool=true: Whether to return results in table format

Returns

Parameters information for the selected models based on the specified settings.

Examples

julia
julia> using Sindbad

julia> # Get parameters for selected models
julia> # param_table = getParameters(selected_models, Float64, model_timestep)
Code
julia
function getParameters end

function getParameters(selected_models::LongTuple, num_type, model_timestep; return_table=true, show_info=false)
    selected_models = to_tuple(selected_models)
    return getParameters(selected_models, num_type, model_timestep; return_table=return_table, show_info=show_info)
end

function getParameters(selected_models::LongTuple, num_type, model_timestep; return_table=true, show_info=false)
    selected_models = to_tuple(selected_models)
    return getParameters(selected_models, num_type, model_timestep; return_table=return_table, show_info=show_info)
end

function getParameters(selected_models::Tuple, num_type, model_timestep; return_table=true, show_info=false)
    model_names_list = nameof.(typeof.(selected_models))
    constrains = []
    default = []
    name = Symbol[]
    model_approach = Symbol[]
    timescale=String[]
    for obj in selected_models
        k_names = propertynames(obj)
        push!(constrains, SindbadTEM.Processes.bounds(obj)...)
        push!(default, [getproperty(obj, name) for name in k_names]...)
        push!(name, k_names...)
        push!(model_approach, repeat([nameof(typeof(obj))], length(k_names))...)
        push!(timescale, SindbadTEM.Processes.timescale(obj)...)
    end
    # infer types by re-building
    constrains = [c for c in constrains]
    default = [d for d in default]

    nbounds = length(constrains)
    lower = [constrains[i][1] for i in 1:nbounds]
    upper = [constrains[i][2] for i in 1:nbounds]
    
    model = [Symbol(supertype(getproperty(SindbadTEM.Processes, m))) for m in model_approach]
    model_str = string.(model)
    name_full = [join((last(split(model_str[i], ".")), name[i]), ".") for i in 1:nbounds]
    approach_func = [getfield(SindbadTEM.Processes, m) for m in model_approach]
    model_prev = model_approach[1]
    m_id = findall(x-> x==model_prev, model_names_list)[1]
    model_id = map(model_approach) do m
        if m !== model_prev
            model_prev = m
            m_id = findall(x-> x==model_prev, model_names_list)[1]
        end
        m_id
    end

    unts=[]
    unts_ori=[]
    for m in eachindex(name)
        prm_name = Symbol(name[m])
        appr = approach_func[m]()
        p_timescale = SindbadTEM.Processes.timescale(appr, prm_name)
        unit_factor = getUnitConversionForParameter(p_timescale, model_timestep)
        lower[m] = lower[m] * unit_factor
        upper[m] = upper[m] * unit_factor
        if hasproperty(appr, prm_name)
            p_unit = SindbadTEM.Processes.units(appr, prm_name)
            push!(unts_ori, p_unit)
            if ~isone(unit_factor)
                p_unit = replace(p_unit, p_timescale => model_timestep)
            end
            push!(unts, p_unit)
        else
            error("$appr does not have a parameter $prmn")
        end
    end

    # default = num_type.(default)
    lower = map(lower) do low
        if isa(low, Number)
            low = num_type(low)
        else
            low = num_type.(low)
        end
        low
    end
    upper = map(upper) do upp
        if isa(upp, Number)
            upp = num_type(upp)
        else
            upp = num_type.(upp)
        end
        upp
    end
    # lower = num_type.(lower)
    # upper = num_type.(upper)
    timescale_run = map(timescale) do ts
        isempty(ts) ? ts : model_timestep
    end
    checkParameterBounds(name, default, lower, upper, ScaleNone(), p_units=unts, show_info=show_info, model_names=model_approach)
    is_ml = Array{Bool}(undef, length(default))
    dist = Array{String}(undef, length(default))
    p_dist = Array{Array{num_type,1}}(undef, length(default))
    is_ml .= false
    dist .= ""
    p_dist .= [num_type[]]

    output = (; model_id, name, initial=default, default,optimized=default, lower, upper, timescale_run=timescale_run, units=unts, timescale_ori=timescale, units_ori=unts_ori, model, model_approach, approach_func, name_full, is_ml, dist, p_dist)
    output = return_table ? Table(output) : output
    return output
end

getSindbadDataDepot

Sindbad.Setup.getSindbadDataDepot Function
julia
getSindbadDataDepot(; env_data_depot_var="SINDBAD_DATA_DEPOT", local_data_depot="../data")

Retrieve the Sindbad data depot path.

Arguments

  • env_data_depot_var: Environment variable name for the data depot (default: "SINDBAD_DATA_DEPOT")

  • local_data_depot: Local path to the data depot (default: "../data")

Returns

The path to the Sindbad data depot.

Code
julia
function getSindbadDataDepot(; env_data_depot_var="SINDBAD_DATA_DEPOT", local_data_depot="../data")
    data_depot = isabspath(local_data_depot) ? local_data_depot : haskey(ENV, env_data_depot_var) ? ENV[env_data_depot_var] : local_data_depot
    return data_depot
end

getSpinupSequenceWithTypes

Sindbad.Setup.getSpinupSequenceWithTypes Function
julia
getSpinupSequenceWithTypes(seqq, helpers_dates)

Processes the spinup sequence and assigns types for temporal aggregators for spinup forcing.

Arguments:

  • seqq: The spinup sequence from the experiment configuration.

  • helpers_dates: A NamedTuple containing date-related helpers.

Returns:

  • A processed spinup sequence with forcing types for temporal aggregators.
Code
julia
function getSpinupSequenceWithTypes(seqq, helpers_dates)
    seqq_typed = []
    for seq in seqq
        for kk in keys(seq)
            if kk == "forcing"
                skip_sampling = false
                if startswith(kk, helpers_dates.temporal_resolution)
                    skip_sampling = true
                end
                aggregator = create_TimeSampler(helpers_dates.range, to_uppercase_first(seq[kk], "Time"), mean, skip_sampling)
                seq["aggregator"] = aggregator
                seq["aggregator_type"] = TimeNoDiff()
                seq["aggregator_indices"] = [_ind for _ind in vcat(aggregator[1].indices...)]
                seq["n_timesteps"] = length(aggregator[1].indices)
                if occursin("_year", seq[kk])
                    seq["aggregator_type"] = TimeIndexed()
                    seq["n_timesteps"] = length(seq["aggregator_indices"])
                end
            end
            if kk == "spinup_mode"
                seq[kk] = getTypeInstanceForNamedOptions(seq[kk])
            end
            if seq[kk] isa String
                seq[kk] = Symbol(seq[kk])
            end
        end
        optns = in(seq, "options") ? seqp["options"] : (;)
        sst = SpinupSequenceWithAggregator(seq["forcing"], seq["n_repeat"], seq["n_timesteps"], seq["spinup_mode"], optns, seq["aggregator_indices"], seq["aggregator"], seq["aggregator_type"]);
        push!(seqq_typed, sst)
    end
    return seqq_typed
end

getTypeInstanceForCostMetric

Sindbad.Setup.getTypeInstanceForCostMetric Function
julia
getTypeInstanceForCostMetric(mode_name::String)

Retrieves the type instance for a given cost metric based on its name.

Arguments:

  • mode_name::String: The name of the cost metric (e.g., "RMSE", "MAE").

Returns:

  • An instance of the corresponding cost metric type.

Notes:

  • The function converts the cost metric name to a type by capitalizing the first letter of each word and removing underscores.

  • The type is retrieved from the SindbadMetrics module and instantiated.

  • Used for dispatching cost metric calculations in SINDBAD.

Examples

julia
julia> using Sindbad, ErrorMetrics

julia> getTypeInstanceForCostMetric(ErrorMetrics, "MSE")
MSE()

julia> getTypeInstanceForCostMetric(ErrorMetrics, "NSE")
NSE()
Code
julia
function getTypeInstanceForCostMetric(source_module::Module, option_name::String)
    opt_ss = to_uppercase_first(option_name)
    struct_instance = getfield(source_module, opt_ss)()
    return struct_instance
end

getTypeInstanceForFlags

Sindbad.Setup.getTypeInstanceForFlags Function
julia
getTypeInstanceForFlags(option_name::Symbol, option_value, opt_pref="Do")

Generates a type instance for boolean flags based on the flag name and value.

Arguments:

  • option_name::Symbol: The name of the flag (e.g., :run_optimization, :save_info).

  • option_value: A boolean value (true or false) indicating the state of the flag.

  • opt_pref::String: (Optional) A prefix for the type name. Defaults to "Do".

Returns:

  • An instance of the corresponding type:
    • If option_value is true, the type name is prefixed with opt_pref (e.g., DoRunOptimization).

    • If option_value is false, the type name is prefixed with opt_pref * "Not" (e.g., DoNotRunOptimization).

Notes:

  • The function converts the flag name to a string, capitalizes the first letter of each word, and appends the appropriate prefix (Do or DoNot).

  • The resulting type is retrieved from the Setup module and instantiated.

  • This is used for type-based dispatch in SINDBAD's model execution.

Examples

julia
julia> using Sindbad

julia> # Get type instance for a flag set to true
julia> # flag_type = getTypeInstanceForFlags(:run_optimization, true)
julia> # Returns: DoRunOptimization()

julia> # Get type instance for a flag set to false
julia> # flag_type = getTypeInstanceForFlags(:run_optimization, false)
julia> # Returns: DoNotRunOptimization()
Code
julia
function getTypeInstanceForFlags(option_name::Symbol, option_value, opt_pref="Do")
    opt_s = string(option_name)
    structname = to_uppercase_first(opt_s, opt_pref)
    if !option_value
        structname = to_uppercase_first(opt_s, opt_pref*"Not")
    end
    struct_instance = getfield(Setup, structname)()
    return struct_instance
end

getTypeInstanceForNamedOptions

Sindbad.Setup.getTypeInstanceForNamedOptions Function
julia
getTypeInstanceForNamedOptions(option_name)

Retrieves a type instance for a named option based on its string or symbol representation. These options are mainly within the optimization and temporal aggregation.

Arguments:

  • option_name: The name of the option, provided as either a String or a Symbol.

Returns:

  • An instance of the corresponding type from the Setup module.

Notes:

  • If the input is a Symbol, it is converted to a String before processing.

  • The function capitalizes the first letter of each word in the option name and removes underscores to match the type naming convention.

  • This is used for type-based dispatch in SINDBAD's configuration and execution.

  • The type for temporal aggregation is set using get_TimeSampler in Utils. It uses a similar approach and prefixes Time to type.

Examples

julia
julia> using Sindbad

julia> # Get type instance for a named option
julia> # option_type = getTypeInstanceForNamedOptions("metric_sum")
julia> # Returns: MetricSum() type instance
julia> # 
julia> # Examples of conversions:
julia> # - "cost_metric": "NSE_inv" → NSEInv type
julia> # - "temporal_data_aggr": "month_anomaly" → MonthAnomaly type
Code
julia
function getTypeInstanceForNamedOptions end

function getTypeInstanceForNamedOptions(option_name::String)
    opt_ss = to_uppercase_first(option_name)
    struct_instance = getfield(Setup, opt_ss)()
    return struct_instance
end

function getTypeInstanceForNamedOptions(option_name::String)
    opt_ss = to_uppercase_first(option_name)
    struct_instance = getfield(Setup, opt_ss)()
    return struct_instance
end

function getTypeInstanceForNamedOptions(option_name::Symbol)
    return getTypeInstanceForNamedOptions(string(option_name))
end

perturbParameters

Sindbad.Setup.perturbParameters Function
julia
perturbParameters(x::AbstractVector, lower::AbstractVector, upper::AbstractVector, percent_range::Tuple{Float64,Float64}=(0.0, 0.1))

Modify each element of vector x by a random percentage within percent_range, while ensuring the result stays within the bounds defined by lower and upper vectors.

Arguments

  • x: Vector to modify

  • lower: Vector of lower bounds

  • upper: Vector of upper bounds

  • percent_range: Tuple of (min_percent, max_percent) for random modification (default: (0.0, 0.1))

Returns

  • Modified vector x (modified in-place)

Example

julia
x = [1.0, 2.0, 3.0]
lower = [0.5, 1.5, 2.5]
upper = [1.5, 2.5, 3.5]
modify_within_bounds!(x, lower, upper, (0.0, 0.1))  # Modify by 0-10%
Code
julia
function perturbParameters(x::AbstractVector, lower::AbstractVector, upper::AbstractVector; percent_range::Tuple{Float64,Float64}=(0.0, 0.1))
    @assert length(x) == length(lower) == length(upper) "Vectors must have the same length"
    @assert all(lower .<= upper) "Lower bounds must be less than or equal to upper bounds"
    @assert all(lower .<= x .<= upper) "Initial values must be within bounds"
    @assert percent_range[1] >= 0.0 "Minimum percent must be non-negative"
    @assert percent_range[2] >= percent_range[1] "Maximum percent must be greater than or equal to minimum percent"

    min_pct, max_pct = percent_range
    for i in eachindex(x)
        # Generate random percentage within range
        pct = min_pct + rand() * (max_pct - min_pct)
        
        # Calculate new value
        new_val = x[i] * (1 + pct)
        
        # Ensure value stays within bounds
        x[i] = clamp(new_val, lower[i], upper[i])
    end
    return x
end

prepCostOptions

Sindbad.Setup.prepCostOptions Function
julia
prepCostOptions(observations, cost_options, ::CostMethod)

Prepares cost options for optimization by filtering variables with insufficient data points and setting up the required configurations.

Arguments:

  • observations: A NamedTuple or a vector of arrays containing observation data, uncertainties, and masks used for calculating performance metrics or loss.

  • cost_options: A table listing each observation constraint and its configuration for calculating the loss or performance metric.

  • ::CostMethod: A type indicating the cost function method.

Returns:

  • A filtered table of cost_options containing only valid variables with sufficient data points.

cost methods:

CostMethod

Abstract type for cost calculation methods in SINDBAD

Available methods/subtypes:

  • CostModelObs: cost calculation between model output and observations

  • CostModelObsLandTS: cost calculation between land model output and time series observations

  • CostModelObsMT: multi-threaded cost calculation between model output and observations

  • CostModelObsPriors: cost calculation between model output, observations, and priors. NOTE THAT THIS METHOD IS JUST A PLACEHOLDER AND DOES NOT CALCULATE PRIOR COST PROPERLY YET


Extended help

Examples

julia
julia> using Sindbad

julia> # Prepare cost options for optimization
julia> # filtered_cost_options = prepCostOptions(observations, cost_options, CostModelObs())

Notes:

  • The function iterates through the observation variables and checks if the number of valid data points meets the minimum threshold specified in cost_options.min_data_points.

  • Variables with insufficient data points are excluded from the returned cost_options.

  • The function modifies the cost_options table by adding:

    • valids: Indices of valid data points for each variable.

    • is_valid: A boolean flag indicating whether the variable meets the minimum data point requirement.

  • Unnecessary fields such as min_data_points, temporal_data_aggr, and aggr_func are removed from the final cost_options.

Code
julia
function prepCostOptions end


function prepCostOptions(observations, cost_options)
    return prepCostOptions(observations, cost_options, CostModelObs())
end

function prepCostOptions(observations, cost_options)
    return prepCostOptions(observations, cost_options, CostModelObs())
end

function prepCostOptions(observations, cost_options, ::CostModelObsPriors)
    return prepCostOptions(observations, cost_options, CostModelObs())
end

function prepCostOptions(observations, cost_options, ::CostModelObs)
    valids=[]
    is_valid = []
    vars = cost_options.variable
    obs_inds = cost_options.obs_ind
    min_data_points = cost_options.min_data_points
    for vi in eachindex(vars)
        obs_ind_start = obs_inds[vi]
        min_point = min_data_points[vi]
        y = observations[obs_ind_start]
= observations[obs_ind_start+1]
        idxs = Array(.!is_invalid_number.(y .* yσ))
        total_point = sum(idxs)
        if total_point < min_point
            push!(is_valid, false)
        else
            push!(is_valid, true)
        end
        push!(valids, idxs)
    end
    cost_options = set_namedtuple_field(cost_options, (:valids, valids))
    cost_options = set_namedtuple_field(cost_options, (:is_valid, is_valid))
    cost_options = drop_namedtuple_fields(cost_options, (:min_data_points, :temporal_data_aggr, :aggr_func,))
    cost_option_table = Table(cost_options)
    cost_options_table_filtered = filter(row -> row.is_valid === true , cost_option_table)
    return cost_options_table_filtered
end

readConfiguration

Sindbad.Setup.readConfiguration Function
julia
readConfiguration(info_exp::AbstractDict, base_path::String)

Reads the experiment configuration files (JSON or CSV) and returns a dictionary.

Arguments:

  • info_exp::AbstractDict: The experiment configuration dictionary.

  • base_path::String: The base path for resolving relative file paths.

Returns:

  • A dictionary containing the parsed configuration files.
Code
julia
function readConfiguration(info_exp::AbstractDict, base_path::String)
    info = DataStructures.OrderedDict()
    print_info(readConfiguration, @__FILE__, @__LINE__, "reading configuration files")
    for (k, v)  info_exp["experiment"]["basics"]["config_files"]
        config_path = joinpath(base_path, v)
        print_info(nothing, @__FILE__, @__LINE__, "→→→    `$(k)` ::: `$(config_path)`")
        info_exp["experiment"]["basics"]["config_files"][k] = config_path
        if endswith(v, ".json")
            tmp = parsefile(config_path; dicttype=DataStructures.OrderedDict)
            info[k] = removeComments(tmp) # remove on first level
        elseif endswith(v, ".csv")
            prm = CSV.File(config_path)
            tmp = Table(prm)
            info[k] = tmp
        end
    end

    # rm second level
    for (k, v)  info
        if typeof(v) <: Dict
            ks = keys(info[k])
            tmpDict = DataStructures.OrderedDict()
            for ki  ks
                tmpDict[ki] = removeComments(info[k][ki])
            end
            info[k] = tmpDict
        end
    end
    info["experiment"] = info_exp["experiment"]
    return info
end

scaleParameters

Sindbad.Setup.scaleParameters Function
julia
scaleParameters(parameter_table, <: ParameterScaling)

Scale parameters from the input table using default scaling factors.

Arguments

  • parameter_table: Table containing parameters to be scaled

  • ParameterScaling: Type indicating the scaling strategy to be used

    • ::ScaleDefault: Type indicating scaling by default values

    • ::ScaleBounds: Type parameter indicating scaling by parameter bounds

    • ::ScaleNone: Type parameter indicating no scaling should be applied

Returns

Scaled parameters and their bounds according to default scaling factors

Code
julia
function scaleParameters end

function scaleParameters(parameter_table, _sc::ScaleNone)
    init = copy(parameter_table.initial)
    ub = copy(parameter_table.upper)  # upper bounds
    lb = copy(parameter_table.lower)   # lower bounds
    checkParameterBounds(parameter_table.name, init, lb, ub, _sc, p_units=parameter_table.units, show_info=true, model_names=parameter_table.model_approach)
    return (init, lb, ub)
end

function scaleParameters(parameter_table, _sc::ScaleNone)
    init = copy(parameter_table.initial)
    ub = copy(parameter_table.upper)  # upper bounds
    lb = copy(parameter_table.lower)   # lower bounds
    checkParameterBounds(parameter_table.name, init, lb, ub, _sc, p_units=parameter_table.units, show_info=true, model_names=parameter_table.model_approach)
    return (init, lb, ub)
end

function scaleParameters(parameter_table, _sc::ScaleDefault)
    init = abs.(copy(parameter_table.initial))
    ub = copy(parameter_table.upper ./ init)   # upper bounds
    lb = copy(parameter_table.lower ./ init)   # lower bounds
    init = parameter_table.initial ./ init
    checkParameterBounds(parameter_table.name, init, lb, ub, _sc, p_units=parameter_table.units, show_info=true, model_names=parameter_table.model_approach)
    return (init, lb, ub)
end

function scaleParameters(parameter_table, _sc::ScaleBounds)
    init = copy(parameter_table.initial)
    ub = copy(parameter_table.upper)  # upper bounds
    lb = copy(parameter_table.lower)   # lower bounds
    init = (init - lb)  ./ (ub - lb)
    lb = zero(lb)
    ub = one.(ub)
    checkParameterBounds(parameter_table.name, init, lb, ub, _sc, p_units=parameter_table.units, show_info=true, model_names=parameter_table.model_approach)
    return (init, lb, ub)
end

setHybridInfo

Sindbad.Setup.setHybridInfo Function
julia
setHybridInfo(info::NamedTuple)

Processes and sets up the hybrid experiment information in the experiment configuration.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with hybrid experiment information added.
Code
julia
function setHybridInfo(info::NamedTuple)
    print_info(setHybridInfo, @__FILE__, @__LINE__, "setting info for hybrid machine-learning + TEM experiment...", n_m=1)
    # hybrid_options = info.settings.hybrid
    # set
    info = setHybridOptions(info, :ml_model)
    info = setHybridOptions(info, :ml_training)
    info = setHybridOptions(info, :ml_gradient)
    info = setHybridOptions(info, :ml_optimizer)
    checkpoint_path = ""
    hybrid_root = joinpath(dirname(info.output.dirs.data),"hybrid")
    mkpath(hybrid_root)
    if info.settings.hybrid.save_checkpoint
        checkpoint_path = joinpath(hybrid_root,"training_checkpoints")
        mkpath(checkpoint_path)
    end

    output_dirs = info.temp.output.dirs
    output_dirs = (; output_dirs..., hybrid=(; root=hybrid_root, checkpoint=checkpoint_path))
    info = (; info..., temp = (info.temp..., output = (; info.temp.output..., dirs = output_dirs)))

    fold_type = CalcFoldFromSplit()
    fold_path = ""
    which_fold = 1
    ml_training = info.settings.hybrid.ml_training
    if hasproperty(ml_training, :fold_path)
        fold_path_file = ml_training.fold_path
        fold_path = isnothing(fold_path_file) ? fold_path : fold_path_file
        if !isempty(fold_path)
            fold_type = LoadFoldFromFile()
            if !isabspath(fold_path)
                fold_path = joinpath(info.temp.experiment.dirs.settings, fold_path)
            end
        end
    end
    if hasproperty(ml_training, :which_fold)
        which_fold = ml_training.which_fold
    end

    fold_s = (; fold_path, which_fold, fold_type)
    info = set_namedtuple_subfield(info, :hybrid, (:fold, fold_s))

    replace_value_for_gradient = hasproperty(info.settings.hybrid, :replace_value_for_gradient) ? info.settings.hybrid.replace_value_for_gradient : 0.0

    info = set_namedtuple_subfield(info, :hybrid, (:replace_value_for_gradient, info.temp.helpers.numbers.num_type(replace_value_for_gradient)))


    covariates_path = getAbsDataPath(info.temp, info.settings.hybrid.covariates.path)
    covariates = (; path=covariates_path, variables=info.settings.hybrid.covariates.variables)
    info = set_namedtuple_subfield(info, :hybrid, (:covariates, covariates))
    info = set_namedtuple_subfield(info, :hybrid, (:random_seed, info.settings.hybrid.random_seed))

    return info
end

setModelOutput

Sindbad.Setup.setModelOutput Function
julia
setModelOutput(info::NamedTuple)

Sets the output variables to be written and stored based on the experiment configuration.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with output variables and depth information added.
Code
julia
function setModelOutput(info::NamedTuple)
    print_info(setModelOutput, @__FILE__, @__LINE__, "setting Model Output Info...")
    output_vars = collect(propertynames(info.settings.experiment.model_output.variables))
    info = (; info..., temp=(; info.temp..., output=getDepthInfoAndVariables(info, output_vars)))
    return info
end

function setModelOutputLandAll(info, land)
    output_vars = getAllLandVars(land)
    depth_info = map(output_vars) do v_full_pair
        v_index = findfirst(x -> first(x) === first(v_full_pair) && last(x) === last(v_full_pair), info.output.variables)
        dim_name = nothing
        dim_size = nothing
        if ~isnothing(v_index)
            dim_name = last(info.output.depth_info[v_index])
            dim_size = first(info.output.depth_info[v_index])
        else
            field_name = first(v_full_pair)
            v_name = last(v_full_pair)
            dim_name = string(v_name) * "_idx"
            land_field = getproperty(land, field_name)
            if hasproperty(land_field, v_name)
                land_subfield = getproperty(land_field, v_name)
                if isa(land_subfield, AbstractArray)
                    dim_size = length(land_subfield)
                elseif isa(land_subfield, Number)
                    dim_size = 1
                else
                    dim_size = 0
                end
            end
        end
        dim_size, dim_name
    end
    info = @set info.output.variables = output_vars
    info = @set info.output.depth_info = depth_info
    return info
end

setModelOutputLandAll

Sindbad.Setup.setModelOutputLandAll Function
julia
setModelOutputLandAll(info, land)

Retrieves all model variables from land and overwrites the output information in info.

Arguments:

  • info: A NamedTuple containing experiment configuration and helper information.

  • land: A core SINDBAD NamedTuple containing variables for a given time step.

Returns:

  • The updated info NamedTuple with output variables and depth information updated.
Code
julia
function setModelOutputLandAll(info, land)
    output_vars = getAllLandVars(land)
    depth_info = map(output_vars) do v_full_pair
        v_index = findfirst(x -> first(x) === first(v_full_pair) && last(x) === last(v_full_pair), info.output.variables)
        dim_name = nothing
        dim_size = nothing
        if ~isnothing(v_index)
            dim_name = last(info.output.depth_info[v_index])
            dim_size = first(info.output.depth_info[v_index])
        else
            field_name = first(v_full_pair)
            v_name = last(v_full_pair)
            dim_name = string(v_name) * "_idx"
            land_field = getproperty(land, field_name)
            if hasproperty(land_field, v_name)
                land_subfield = getproperty(land_field, v_name)
                if isa(land_subfield, AbstractArray)
                    dim_size = length(land_subfield)
                elseif isa(land_subfield, Number)
                    dim_size = 1
                else
                    dim_size = 0
                end
            end
        end
        dim_size, dim_name
    end
    info = @set info.output.variables = output_vars
    info = @set info.output.depth_info = depth_info
    return info
end

setOptimization

Sindbad.Setup.setOptimization Function
julia
setOptimization(info::NamedTuple)

Sets up optimization-related fields in the experiment configuration.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with optimization-related fields added.

Notes:

  • Configures cost metrics, optimization parameters, algorithms, and variables to store during optimization.

  • Validates the parameters to be optimized against the model structure.

Code
julia
function setOptimization(info::NamedTuple)
    print_info(setOptimization, @__FILE__, @__LINE__, "setting ParameterOptimization and Observation Info...")
    info = set_namedtuple_field(info, (:optimization, (;)))

    # set information related to cost metrics for each variable
    info = set_namedtuple_subfield(info, :optimization, (:model_parameter_default, info.settings.optimization.model_parameter_default))
    info = set_namedtuple_subfield(info, :optimization, (:observational_constraints, info.settings.optimization.observational_constraints))

    n_threads_cost = 1
    if info.settings.optimization.optimization_cost_threaded > 0 && info.settings.experiment.flags.run_optimization
        n_threads_cost = info.settings.optimization.optimization_cost_threaded > 1 ? info.settings.optimization.optimization_cost_threaded : Threads.nthreads()
        # overwrite land array type when threaded optimization is set
        info = @set info.temp.helpers.run.land_output_type = PreAllocArrayMT()
        # info = set_namedtuple_subfield(info,
        # :optimization,
        # (:n_threads_cost, n_threads_cost))
    end

    # get types for optimization run options
    multi_constraint_method = getTypeInstanceForNamedOptions(info.settings.optimization.multi_constraint_method)
    cost_method = getTypeInstanceForNamedOptions(info.settings.optimization.optimization_cost_method)
    scaling_method = isnothing(info.settings.optimization.optimization_parameter_scaling) ? "scale_none" : info.settings.optimization.optimization_parameter_scaling
    parameter_scaling = getTypeInstanceForNamedOptions(scaling_method)
    optimization_types = (; cost_method, parameter_scaling, multi_constraint_method, n_threads_cost)
    info = set_namedtuple_subfield(info, :optimization, (:run_options, optimization_types))
        
    # check and set the list of parameters to be optimized
    
    # set algorithm related options
    info = setAlgorithmOptions(info, :algorithm_optimization)
    info = setAlgorithmOptions(info, :algorithm_sensitivity_analysis)
    parameter_table = getOptimizationParametersTable(info.temp.models.parameter_table, info.settings.optimization.model_parameter_default, info.settings.optimization.model_parameters_to_optimize)
    
    checkOptimizedParametersInModels(info, parameter_table)

    # use no scaling while checking bounds
    checkParameterBounds(parameter_table.name, parameter_table.initial, parameter_table.lower, parameter_table.upper, ScaleNone(), p_units=parameter_table.units, show_info=true, model_names=parameter_table.model_approach)

    # get the variables to be used during optimization
    obs_vars, optim_vars, store_vars, model_vars = getConstraintNames(info.settings.optimization)
    vars_info = (;)
    vars_info = set_namedtuple_field(vars_info, (:obs, obs_vars))
    vars_info = set_namedtuple_field(vars_info, (:optimization, optim_vars))
    vars_info = set_namedtuple_field(vars_info, (:store, store_vars))
    vars_info = set_namedtuple_field(vars_info, (:model, model_vars))
    info = set_namedtuple_subfield(info, :optimization, (:variables, (vars_info)))
    info = updateVariablesToStore(info)
    cost_options = getCostOptions(info.settings.optimization, vars_info, info.temp.output.variables, info.temp.helpers.numbers, info.temp.helpers.dates)
    info = set_namedtuple_subfield(info, :optimization, (:cost_options, cost_options))
    info = set_namedtuple_subfield(info, :optimization, (:parameter_table, parameter_table))
    optimization_info = info.optimization
    optimization_info = drop_namedtuple_fields(optimization_info, (:model_parameter_default, :model_parameters_to_optimize, :observational_constraints, :variables))
    info = (; info..., optimization = optimization_info)
    return info
end

setOrderedSelectedModels

Sindbad.Setup.setOrderedSelectedModels Function
julia
setOrderedSelectedModels(info::NamedTuple)

Retrieves and orders the list of selected models based on the configuration in model_structure.json.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with the ordered list of selected models added to info.temp.models.

Notes:

  • Ensures consistency by validating the selected models using checkSelectedModels.

  • Orders the models as specified in standard_sindbad_model.

Code
julia
function setOrderedSelectedModels(info::NamedTuple)
    print_info(setOrderedSelectedModels, @__FILE__, @__LINE__, "setting Ordered Selected Models...")
    selected_models = collect(propertynames(info.settings.model_structure.models))
    sindbad_models = getAllSindbadModels(info, selected_models=selected_models, selected_models_info=info.settings.model_structure.models)
    checkSelectedModels(sindbad_models, selected_models)
    t_repeat_models = getModelImplicitTRepeat(info, selected_models)
    # checkSelectedModels(sindbad_models, selected_models)
    order_selected_models = []
    for msm  sindbad_models
        if msm in selected_models
            push!(order_selected_models, msm)
        end
    end
    @debug "     setupInfo: creating initial out/land..."

    info = (; info..., temp=(; info.temp..., models=(; sindbad_models=sindbad_models, selected_models=Table((; model=[order_selected_models...],t_repeat=t_repeat_models)))))
    return info
end

setPoolsInfo

Sindbad.Setup.setPoolsInfo Function
julia
setPoolsInfo(info::NamedTuple)

Generates info.temp.helpers.pools and info.pools.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with pool-related fields added.

Notes:

  • info.temp.helpers.pools is used in the models.

  • info.pools is used for instantiating the pools for the initial output tuple.

Code
julia
function setPoolsInfo(info::NamedTuple)
    print_info(setPoolsInfo, @__FILE__, @__LINE__, "setting Pools Info...")
    elements = keys(info.settings.model_structure.pools)
    tmp_states = (;)
    hlp_states = (;)
    model_array_type = getfield(Types, to_uppercase_first(info.settings.experiment.exe_rules.model_array_type, "ModelArray"))()
    num_type = info.temp.helpers.numbers.num_type
    for element  elements
        vals_tuple = (;)
        vals_tuple = set_namedtuple_field(vals_tuple, (:zix, (;)))
        vals_tuple = set_namedtuple_field(vals_tuple, (:self, (;)))
        vals_tuple = set_namedtuple_field(vals_tuple, (:all_components, (;)))
        elSymbol = Symbol(element)
        tmp_elem = (;)
        hlp_elem = (;)
        tmp_states = set_namedtuple_field(tmp_states, (elSymbol, (;)))
        hlp_states = set_namedtuple_field(hlp_states, (elSymbol, (;)))
        pool_info = getfield(getfield(info.settings.model_structure.pools, element), :components)
        nlayers = Int64[]
        layer_thicknesses = num_type[]
        layer = Int64[]
        inits = []
        sub_pool_name = Symbol[]
        main_pool_name = Symbol[]
        main_pools = Symbol.(keys(getfield(getfield(info.settings.model_structure.pools, element), :components)))
        layer_thicknesses, nlayers, layer, inits, sub_pool_name, main_pool_name = getPoolInformation(main_pools, pool_info, layer_thicknesses, nlayers, layer, inits, sub_pool_name, main_pool_name)

        # set empty tuple fields
        tpl_fields = (:components, :zix, :initial_values, :layer_thickness)
        for _tpl  tpl_fields
            tmp_elem = set_namedtuple_field(tmp_elem, (_tpl, (;)))
        end
        hlp_elem = set_namedtuple_field(hlp_elem, (:layer_thickness, (;)))
        # hlp_elem = set_namedtuple_field(hlp_elem, (:n_layers, (;)))
        hlp_elem = set_namedtuple_field(hlp_elem, (:zix, (;)))
        hlp_elem = set_namedtuple_field(hlp_elem, (:components, (;)))
        hlp_elem = set_namedtuple_field(hlp_elem, (:all_components, (;)))
        hlp_elem = set_namedtuple_field(hlp_elem, (:zeros, (;)))
        hlp_elem = set_namedtuple_field(hlp_elem, (:ones, (;)))

        # main pools
        for main_pool  main_pool_name
            zix = Int[]
            initial_values = []
            # initial_values = num_type[]
            components = Symbol[]
            for (ind, par)  enumerate(sub_pool_name)
                if startswith(String(par), String(main_pool))
                    push!(zix, ind)
                    push!(components, sub_pool_name[ind])
                    push!(initial_values, inits[ind])
                end
            end
            initial_values = createArrayofType(initial_values, Nothing[], num_type, nothing, true, model_array_type)

            zix = Tuple(zix)

            tmp_elem = set_namedtuple_subfield(tmp_elem, :components, (main_pool, Tuple(components)))
            tmp_elem = set_namedtuple_subfield(tmp_elem, :zix, (main_pool, zix))
            tmp_elem = set_namedtuple_subfield(tmp_elem, :initial_values, (main_pool, initial_values))
            # hlp_elem = set_namedtuple_subfield(hlp_elem, :n_layers, (main_pool, length(zix)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :zix, (main_pool, zix))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :components, (main_pool, Tuple(components)))
            onetyped = createArrayofType(ones(size(initial_values)), Nothing[], num_type, nothing, true, model_array_type)
            hlp_elem = set_namedtuple_subfield(hlp_elem, :zeros, (main_pool, zero(onetyped)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :ones, (main_pool, onetyped))
        end

        # subpools
        unique_sub_pools = Symbol[]
        for _sp  sub_pool_name
            if _sp  unique_sub_pools
                push!(unique_sub_pools, _sp)
            end
        end
        for sub_pool  unique_sub_pools
            zix = Int[]
            initial_values = []
            components = Symbol[]
            ltck = num_type[]
            # ltck = []
            for (ind, par)  enumerate(sub_pool_name)
                if par == sub_pool
                    push!(zix, ind)
                    push!(initial_values, inits[ind])
                    push!(components, sub_pool_name[ind])
                    push!(ltck, layer_thicknesses[ind])
                end
            end
            zix = Tuple(zix)
            initial_values = createArrayofType(initial_values, Nothing[], num_type, nothing, true, model_array_type)
            tmp_elem = set_namedtuple_subfield(tmp_elem, :components, (sub_pool, Tuple(components)))
            tmp_elem = set_namedtuple_subfield(tmp_elem, :zix, (sub_pool, zix))
            tmp_elem = set_namedtuple_subfield(tmp_elem, :initial_values, (sub_pool, initial_values))
            tmp_elem = set_namedtuple_subfield(tmp_elem, :layer_thickness, (sub_pool, Tuple(ltck)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :layer_thickness, (sub_pool, Tuple(ltck)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :zix, (sub_pool, zix))
            # hlp_elem = set_namedtuple_subfield(hlp_elem, :n_layers, (sub_pool, length(zix)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :components, (sub_pool, Tuple(components)))
            onetyped = createArrayofType(ones(size(initial_values)), Nothing[], num_type, nothing, true, model_array_type)
            # onetyped = ones(length(initial_values))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :zeros, (sub_pool, zero(onetyped)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :ones, (sub_pool, onetyped))
        end

        ## combined pools
        combine_pools = (getfield(getfield(info.settings.model_structure.pools, element), :combine))
        do_combine = true
        tmp_elem = set_namedtuple_field(tmp_elem, (:combine, (; docombine=true, pool=Symbol(combine_pools))))
        if do_combine
            combined_pool_name = Symbol.(combine_pools)
            create = Symbol[combined_pool_name]
            components = Symbol[]
            for _sp  sub_pool_name
                if _sp  components
                    push!(components, _sp)
                end
            end
            initial_values = inits
            initial_values = createArrayofType(initial_values, Nothing[], num_type, nothing, true, model_array_type)
            zix = collect(1:1:length(main_pool_name))
            zix = Tuple(zix)

            tmp_elem = set_namedtuple_subfield(tmp_elem, :components, (combined_pool_name, Tuple(components)))
            tmp_elem = set_namedtuple_subfield(tmp_elem, :zix, (combined_pool_name, zix))
            tmp_elem = set_namedtuple_subfield(tmp_elem, :initial_values, (combined_pool_name, initial_values))
            # hlp_elem = set_namedtuple_subfield(hlp_elem, :n_layers, (combined_pool_name, length(zix)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :zix, (combined_pool_name, zix))
            onetyped = createArrayofType(ones(size(initial_values)), Nothing[], num_type, nothing, true, model_array_type)
            all_components = Tuple([_k for _k in keys(tmp_elem.zix) if _k !== combined_pool_name])
            hlp_elem = set_namedtuple_subfield(hlp_elem, :all_components, (combined_pool_name, all_components))
            vals_tuple = set_namedtuple_subfield(vals_tuple, :zix, (combined_pool_name, Val(hlp_elem.zix)))
            vals_tuple = set_namedtuple_subfield(vals_tuple, :self, (combined_pool_name, Val(combined_pool_name)))
            vals_tuple = set_namedtuple_subfield(vals_tuple, :all_components, (combined_pool_name, Val(all_components)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :components, (combined_pool_name, Tuple(components)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :zeros, (combined_pool_name, zero(onetyped)))
            hlp_elem = set_namedtuple_subfield(hlp_elem, :ones, (combined_pool_name, onetyped))
        else
            create = Symbol.(unique_sub_pools)
        end

        # check if additional variables exist
        if hasproperty(getfield(info.settings.model_structure.pools, element), :state_variables)
            state_variables = getfield(getfield(info.settings.model_structure.pools, element), :state_variables)
            tmp_elem = set_namedtuple_field(tmp_elem, (:state_variables, state_variables))
        end
        arraytype = :view
        if hasproperty(info.settings.experiment.exe_rules, :model_array_type)
            arraytype = Symbol(info.settings.experiment.exe_rules.model_array_type)
        end
        tmp_elem = set_namedtuple_field(tmp_elem, (:arraytype, arraytype))
        tmp_elem = set_namedtuple_field(tmp_elem, (:create, create))
        tmp_states = set_namedtuple_field(tmp_states, (elSymbol, tmp_elem))
        hlp_states = set_namedtuple_field(hlp_states, (elSymbol, hlp_elem))
    end
    hlp_new = (;)
    # tc_print(hlp_states)
    eleprops = propertynames(hlp_states)
    if :carbon in eleprops && :water in eleprops
        for prop  propertynames(hlp_states.carbon)
            cfield = getproperty(hlp_states.carbon, prop)
            wfield = getproperty(hlp_states.water, prop)
            cwfield = (; cfield..., wfield...)
            if prop == :vals
                cwfield = (;)
                for subprop in propertynames(cfield)
                    csub = getproperty(cfield, subprop)
                    wsub = getproperty(wfield, subprop)
                    cwfield = set_namedtuple_field(cwfield, (subprop, (; csub..., wsub...)))
                end
            end
            hlp_new = set_namedtuple_field(hlp_new, (prop, cwfield))
        end
    elseif :carbon in eleprops && :water eleprops
        hlp_new = hlp_states.carbon
    elseif :carbon eleprops && :water in eleprops
        hlp_new = hlp_states.water
    else
        hlp_new = hlp_states
    end

    # get the number of layers per pool 
    n_layers = NamedTuple(map(propertynames(hlp_new.ones)) do one_pool
        n_pool = num_type(length(getproperty(hlp_new.ones, one_pool)))
        Pair(one_pool, n_pool)
    end
    )
    hlp_new = (hlp_new..., n_layers=n_layers)

    info = (; info..., pools=tmp_states, temp=(; info.temp..., helpers=(; info.temp.helpers..., pools=hlp_new)))
    return info
end

setSpinupAndForwardModels

Sindbad.Setup.setSpinupAndForwardModels Function
julia
setSpinupAndForwardModels(info::NamedTuple)

Configures the spinup and forward models for the experiment.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with the spinup and forward models added to info.temp.models.

Notes:

  • Allows for faster spinup by turning off certain models using the use_in_spinup flag in model_structure.json.

  • Ensures that spinup models are a subset of forward models.

  • Updates model parameters if additional parameter values are provided in the experiment configuration.

Code
julia
function setSpinupAndForwardModels(info::NamedTuple)
    print_info(setSpinupAndForwardModels, @__FILE__, @__LINE__, "setting Spinup and Forward Models...")
    selected_approach_forward = ()
    is_spinup = Int64[]
    order_selected_models = info.temp.models.selected_models.model
    default_model = getfield(info.settings.model_structure, :default_model)
    for sm  order_selected_models
        model_info = getfield(info.settings.model_structure.models, sm)
        selected_approach = model_info.approach
        selected_approach = String(sm) * "_" * selected_approach
        selected_approach_func = getTypedModel(Symbol(selected_approach), info.temp.helpers.dates.temporal_resolution, info.temp.helpers.numbers.num_type)
        selected_approach_forward = (selected_approach_forward..., selected_approach_func)
        if :use_in_spinup in propertynames(model_info)
            use_in_spinup = model_info.use_in_spinup
        else
            use_in_spinup = default_model.use_in_spinup
        end
        if use_in_spinup == true
            push!(is_spinup, 1)
        else
            push!(is_spinup, 0)
        end
    end
    # change is_spinup to a vector of indices
    is_spinup = findall(is_spinup .== 1)

    # update the parameters of the approaches if a parameter value has been added from the experiment configuration
    default_parameter_table = getParameters(selected_approach_forward, info.temp.helpers.numbers.num_type, info.temp.helpers.dates.temporal_resolution)

    input_parameter_table = nothing
    if hasproperty(info.settings.model_structure, :parameter_table) && !isempty(info.settings.model_structure.parameter_table)
        print_info(setSpinupAndForwardModels, @__FILE__, @__LINE__, "---using input parameters from model_structure.parameter_table in replace_info", n_m=20)

        input_parameter_table = info.settings.model_structure.parameter_table
    elseif hasproperty(info[:settings], :parameters) && !isempty(info.settings.parameters)
        print_info(setSpinupAndForwardModels, @__FILE__, @__LINE__, "     ---using input parameters from settings.parameters passed from CSV input file", n_m=20)
        input_parameter_table = info.settings.parameters
    end
    updated_parameter_table = copy(default_parameter_table)
    if !isnothing(input_parameter_table)
        if !isa(input_parameter_table, Table)
            error("input_parameter_table must be a Table, but its type is $(typeof(input_parameter_table)). Fix the input error in the source (table or csv file) to continue...")
        end
        updated_parameter_table = setInputParameters(default_parameter_table, input_parameter_table, info.temp.helpers.dates.temporal_resolution)
        selected_approach_forward = updateModelParameters(updated_parameter_table, selected_approach_forward, updated_parameter_table.optimized)
    end
    info = (; info..., temp=(; info.temp..., models=(; info.temp.models..., forward=selected_approach_forward, is_spinup=is_spinup, parameter_table=updated_parameter_table))) 
    return info
end

setupInfo

Sindbad.Setup.setupInfo Function
julia
setupInfo(info::NamedTuple)

Processes the experiment configuration and sets up all necessary fields for model simulation.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with all necessary fields for model simulation.
Code
julia
function setupInfo(info::NamedTuple)
    print_info(setupInfo, @__FILE__, @__LINE__, "Setting and consolidating Experiment Info...")
    # @show info.settings.model_structure.parameter_table.optimized
    info = setExperimentBasics(info)
    # @info "  setupInfo: setting Output Basics..."
    info = setExperimentOutput(info)
    # @info "  setupInfo: setting Numeric Helpers..."
    info = setNumericHelpers(info)
    # @info "  setupInfo: setting Pools Info..."
    info = setPoolsInfo(info)
    # @info "  setupInfo: setting Dates Helpers..."
    info = setDatesInfo(info)
    # @info "  setupInfo: setting Model Structure..."
    info = setOrderedSelectedModels(info)
    # @info "  setupInfo: setting Spinup and Forward Models..."
    info = setSpinupAndForwardModels(info)

    # @info "  setupInfo:         ...saving Selected Models Code..."
    _ = parseSaveCode(info)

    # add information related to model run
    # @info "  setupInfo: setting Model Run Flags..."
    info = setModelRunInfo(info)
    # @info "  setupInfo: setting Spinup Info..."
    info = setSpinupInfo(info)

    # @info "  setupInfo: setting Model Output Info..."
    info = setModelOutput(info)

    # @info "  setupInfo: creating Initial Land..."
    land_init = createInitLand(info.pools, info.temp)
    info = (; info..., temp=(; info.temp..., helpers=(; info.temp.helpers..., land_init=land_init)))

    if (info.settings.experiment.flags.run_optimization || info.settings.experiment.flags.calc_cost) && hasproperty(info.settings.optimization, :algorithm_optimization)
        # @info "  setupInfo: setting ParameterOptimization and Observation info..."
        info = setOptimization(info)
    else
        parameter_table = info.temp.models.parameter_table
        checkParameterBounds(parameter_table.name, parameter_table.initial, parameter_table.lower, parameter_table.upper, ScaleNone(), p_units=parameter_table.units, show_info=true, model_names=parameter_table.model_approach)
     end

    if hasproperty(info.settings, :hybrid)
        info = setHybridInfo(info)
    end

    if !isnothing(info.settings.experiment.exe_rules.longtuple_size)
        selected_approach_forward = to_longtuple(info.temp.models.forward, info.settings.experiment.exe_rules.longtuple_size)
        info = @set info.temp.models.forward = selected_approach_forward
    end

    print_info(setupInfo, @__FILE__, @__LINE__, "Cleaning Info Fields...")
    data_settings = (; forcing = info.settings.forcing, optimization = info.settings.optimization)
    exe_rules = info.settings.experiment.exe_rules
    info = drop_namedtuple_fields(info, (:model_structure, :experiment, :output, :pools))
    info = (; info..., info.temp...)
    info = set_namedtuple_subfield(info, :experiment, (:data_settings, data_settings))
    info = set_namedtuple_subfield(info, :experiment, (:exe_rules, exe_rules))
    info = drop_namedtuple_fields(info, (:temp, :settings,))
    return info
end

sindbadDefaultOptions

Sindbad.Setup.sindbadDefaultOptions Function
julia
sindbadDefaultOptions(::MethodType)

Retrieves the default configuration options for a given optimization or sensitivity analysis method in SINDBAD.

Arguments:

  • ::MethodType: The method type for which the default options are requested. Supported types include:
    • ParameterOptimizationMethod: General optimization methods.

    • GSAMethod: General global sensitivity analysis methods.

    • GSAMorris: Morris method for global sensitivity analysis.

    • GSASobol: Sobol method for global sensitivity analysis.

    • GSASobolDM: Sobol method with derivative-based measures.

Returns:

  • A NamedTuple containing the default options for the specified method.

Notes:

  • Each method type has its own set of default options, such as the number of trajectories, samples, or design matrix length.

  • For GSASobolDM, the defaults are inherited from GSASobol.

Code
julia
function sindbadDefaultOptions end
# A basic empty options for all SindbadTypes
sindbadDefaultOptions(::SindbadTypes) = (;)

sindbadDefaultOptions(::CMAEvolutionStrategyCMAES) = (; maxfevals = 50)

sindbadDefaultOptions(::GSAMorris) = (; total_num_trajectory = 200, num_trajectory = 15, len_design_mat=10)

sindbadDefaultOptions(::GSASobol) = (; samples = 5, method_options=(; order=[0, 1]), sampler="Sobol", sampler_options=(;))

sindbadDefaultOptions(::GSASobolDM) = sindbadDefaultOptions(GSASobol())

updateModelParameters

Sindbad.Setup.updateModelParameters Function
julia
updateModelParameters(parameter_table::Table, selected_models::Tuple, parameter_vector::AbstractArray)
updateModelParameters(parameter_table::Table, selected_models::LongTuple, parameter_vector::AbstractArray)
updateModelParameters(parameter_to_index::NamedTuple, selected_models::Tuple, parameter_vector::AbstractArray)

Updates the parameters of SINDBAD models based on the provided parameter vector without mutating the original table of parameters.

Arguments:

  • parameter_table::Table: A table of SINDBAD model parameters selected for optimization. Contains parameter names, bounds, and scaling information.

  • selected_models::Tuple: A tuple of all models selected in the given model structure.

  • selected_models::LongTuple: A long tuple of models, which is converted into a standard tuple for processing.

  • parameter_vector::AbstractArray: A vector of parameter values to update the models.

  • parameter_to_index::NamedTuple: A mapping of parameter indices to model names, used for updating specific parameters in the models.

Returns:

  • A tuple of updated models with their parameters modified according to the provided parameter_vector.

Notes:

  • The function supports multiple input formats for selected_models (e.g., LongTuple, NamedTuple) and adapts accordingly.

  • If parameter_table is provided, the function uses it to find and update the relevant parameters for each model.

  • The parameter_to_index variant allows for a more direct mapping of parameters to models, bypassing the need for a parameter table.

  • The generated function variant (::Val{p_vals}) is used for compile-time optimization of parameter updates.

Examples:

  1. Using parameter_table and selected_models:
julia
updated_models = updateModelParameters(parameter_table, selected_models, parameter_vector)
  1. Using parameter_to_index for direct mapping:
julia
updated_models = updateModelParameters(parameter_to_index, selected_models, parameter_vector)

Implementation Details:

  • The function iterates over the models in selected_models and updates their parameters based on the provided parameter_vector.

  • For each model, it checks if the parameter belongs to the model's approach (using parameter_table.model_approach) and updates the corresponding value.

  • The parameter_to_index variant uses a mapping to directly replace parameter values in the models.

  • The generated (with @generated) function variant (::Val{p_vals}) creates a compile-time optimized update process for specific parameters and models.

Code
julia
function updateModelParameters end

function updateModelParameters(parameter_table::Table, selected_models::LongTuple, parameter_vector::AbstractArray)
    selected_models = to_tuple(selected_models)
    return updateModelParameters(parameter_table, selected_models, parameter_vector)
end

function updateModelParameters(parameter_table::Table, selected_models::LongTuple, parameter_vector::AbstractArray)
    selected_models = to_tuple(selected_models)
    return updateModelParameters(parameter_table, selected_models, parameter_vector)
end

function updateModelParameters(parameter_table::Table, selected_models::Tuple, parameter_vector::AbstractArray)
    updatedModels = eltype(selected_models)[]
    namesApproaches = nameof.(typeof.(selected_models)) # a better way to do this?
    for (idx, modelName)  enumerate(namesApproaches)
        approachx = selected_models[idx]
        model_obj = approachx
        newapproachx = if modelName in parameter_table.model_approach
            vars = propertynames(approachx)
            newvals = Pair[]
            for var  vars
                pindex = findall(row -> row.name == var && row.model_approach == modelName,
                    parameter_table)
                pval = getproperty(approachx, var)
                if !isempty(pindex)
                    pval = parameter_vector[pindex[1]]
                end
                push!(newvals, var => pval)
            end
            typeof(approachx)(; newvals...)
        else
            approachx
        end
        push!(updatedModels, newapproachx)
    end
    return (updatedModels...,)
end

function updateModelParameters(parameter_to_index::NamedTuple, selected_models, parameter_vector::AbstractArray)
    map(selected_models) do model
          modelmap = parameter_to_index[nameof(typeof(model))]
          varsreplace = map(i->parameter_vector[i],modelmap)
          ConstructionBase.setproperties(model,varsreplace)
    end
end

updateModels

Sindbad.Setup.updateModels Function
julia
updateModels(parameter_vector, parameter_updater, parameter_scaling_type, selected_models)

Updates the parameters of selected models using the provided parameter vector.

Arguments

  • parameter_vector: Vector containing the new parameter values

  • parameter_updater: Function or object that defines how parameters should be updated

  • parameter_scaling_type: Specifies the type of scaling to be applied to parameters

  • selected_models: Collection of models whose parameters need to be updated

Returns

Updated models with new parameter values

Code
julia
function updateModels(parameter_vector, parameter_updater, parameter_scaling_type, selected_models)
    parameter_vector = backScaleParameters(parameter_vector, parameter_updater, parameter_scaling_type)
    updated_models = updateModelParameters(parameter_updater, selected_models, parameter_vector)
    return updated_models
end

updateVariablesToStore

Sindbad.Setup.updateVariablesToStore Function
julia
updateVariablesToStore(info::NamedTuple)

Updates the output variables to store based on optimization or cost run settings.

Arguments:

  • info: A NamedTuple containing the experiment configuration.

Returns:

  • The updated info NamedTuple with updated output variables.
Code
julia
function updateVariablesToStore(info::NamedTuple)
    output_vars = info.settings.experiment.model_output.variables
    if info.settings.experiment.flags.calc_cost || !info.settings.optimization.subset_model_output
        output_vars = union(String.(keys(output_vars)),
                info.optimization.variables.model)
    else
        output_vars = map(info.optimization.variables.obs) do vo
            vn = getfield(info.optimization.variables.optimization, vo)
            Symbol(string(vn[1]) * "." * string(vn[2]))
        end        
    end
    info = (; info..., temp=(; info.temp..., output=getDepthInfoAndVariables(info, output_vars)))
    return info
end