Extended introduction to the GMSE apply function (gmse_apply)

The gmse_apply function is a flexible function that allows for user-defined sub-functions calling resource, observation, manager, and user models. Where such models are not specified, predefined GMSE sub-models ‘resource’, ‘observation’, ‘manager’, and ‘user’ are run by default. Any type of sub-model (e.g., numerical, individual-based) is permitted as long as the input and output are appropriately specified. Only one time step is simulated per call to gmse_apply, so the function must be looped for simulation over time. Where model parameters are needed but not specified, defaults from GMSE are used. Here we demonstrate some uses of gmse_apply, and how it might be used to simulate myriad management scenarios in silico.

A simple run of gmse_apply() returns one time step of GMSE using predefined sub-models and default parameter values.

sim_1 <- gmse_apply();

For sim_1, the default ‘basic’ results are returned as below, which summarise key values for all sub-models.

print(sim_1);
## $resource_results
## [1] 1075
## 
## $observation_results
## [1] 997.7324
## 
## $manager_results
##          resource_type scaring culling castration feeding help_offspring
## policy_1             1      NA      55         NA      NA             NA
## 
## $user_results
##         resource_type scaring culling castration feeding help_offspring
## Manager             1      NA       0         NA      NA             NA
## user_1              1      NA      18         NA      NA             NA
## user_2              1      NA      18         NA      NA             NA
## user_3              1      NA      18         NA      NA             NA
## user_4              1      NA      18         NA      NA             NA
##         tend_crops kill_crops
## Manager         NA         NA
## user_1          NA         NA
## user_2          NA         NA
## user_3          NA         NA
## user_4          NA         NA

Note that in the case above we have the total abundance of resources returned (sim_1$resource_results), the estimate of resource abundance from the observation function (sim_1$observation_results), the costs the manager sets for the only available action of culling (sim_1$manager_results), and the number of culls attempted by each user (sim_1$user_results). By default, only one resource type is used, but custom sub-functions could potentially allow for models with multiple resource types. Any custom sub-functions can replace GMSE predefined functions, provided that they have appropriately defined inputs and outputs (see GMSE documentation). For example, we can define a very simple logistic growth function to send to res_mod instead.

alt_res <- function(X, K = 2000, rate = 1){
    X_1 <- X + rate*X*(1 - X/K);
    return(X_1);
}

The above function takes in a population size of X and returns a value X_1 based on the population intrinsic growth rate rate and carrying capacity K. Iterating the logistic growth model by itself under default parameter values with a starting population of 100 will cause the population to increase to carrying capacity in ca seven time steps The function can be substituted into gmse_apply to use it instead of the predefined GMSE resource model.

sim_2 <- gmse_apply(res_mod = alt_res, X = 100, rate = 0.3);

The gmse_apply function will find the parameters it needs to run the alt_res function in place of the default resource function, either by running the default function values (e.g., K = 2000) or values specified directly into gmse_apply (e.g., X = 100 and rate = 0.3). If an argument to a custom function is required but not provided either as a default or specified in gmse_apply, then an error will be returned. Results for the above sim_2 are returned below.

print(sim_2);
## $resource_results
## [1] 128
## 
## $observation_results
## [1] 113.3787
## 
## $manager_results
##          resource_type scaring culling castration feeding help_offspring
## policy_1             1      NA      61         NA      NA             NA
## 
## $user_results
##         resource_type scaring culling castration feeding help_offspring
## Manager             1      NA       0         NA      NA             NA
## user_1              1      NA      16         NA      NA             NA
## user_2              1      NA      16         NA      NA             NA
## user_3              1      NA      16         NA      NA             NA
## user_4              1      NA      16         NA      NA             NA
##         tend_crops kill_crops
## Manager         NA         NA
## user_1          NA         NA
## user_2          NA         NA
## user_3          NA         NA
## user_4          NA         NA

How gmse_apply integrates across sub-models

To integrate across different types of sub-models, gmse_apply translates between vectors and arrays between each sub-model. For example, because the default GMSE observation model requires a resource array with particular requirements for column identites, when a resource model sub-function returns a vector, or a list with a named element ‘resource_vector’, this vector is translated into an array that can be used by the observation model. Specifically, each element of the vector identifies the abundance of a resource type (and hence will usually be just a single value denoting abundance of the only focal population). If this is all the information provided, then a ‘resource_array’ will be made with default GMSE parameter values with an identical number of rows to the abundance value (floored if the value is a non-integer; non-default values can also be put into this transformation from vector to array if they are specified in gmse_apply, e.g., through an argument such as lambda = 0.8). Similarly, a resource_array is also translated into a vector after the default individual-based resource model is run, should a custom observation model require simple abundances instead of an array. The same is true of observation_vector and observation_array objects returned by observation models, of manager_vector and manager_array (i.e., COST in the gmse function) objects returned by manager models, and of user_vector and user_array (i.e., ACTION in the gmse function) objects returned by user models. At each step, a translation between the two is made, with necessary adjustments that can be tweaked through arguments to gmse_apply when needed. Alternative observation, manager, and user, sub-models, for example, are defined below; note that each requires a vector from the preceding model.

# Alternative observation sub-model
alt_obs <- function(resource_vector){ 
    X_obs <- resource_vector - 0.1 * resource_vector;
    return(X_obs);
}

# Alternative manager sub-model
alt_man <- function(observation_vector){
    policy <- observation_vector - 1000;
    if(policy < 0){
        policy <- 0;
    }
    return(policy);
}

# Alternative user sub-model
alt_usr <- function(manager_vector){
    harvest <- manager_vector + manager_vector * 0.1;
    return(harvest);
}

All of these sub-models are completely deterministic, so when run with the same parameter combinations, they produce replicable outputs.

gmse_apply(res_mod = alt_res, obs_mod = alt_obs, 
           man_mod = alt_man, use_mod = alt_usr, X = 1000);
## $resource_results
## [1] 1500
## 
## $observation_results
## [1] 1350
## 
## $manager_results
## [1] 350
## 
## $user_results
## [1] 385

Note that the manager_results and user_results are ambiguous here, and can be interpreted as desired – e.g., as total allowable catch and catches made, or as something like costs of catching set by the manager and effort to catching made by the user. Hence, while manger output is set in terms of costs of performing each action, and user output is set in terms of action attempts, this need not be the case when using gmse_apply (though it should be recognised when using default GMSE manager and user functions). GMSE default sub-models can be added in at any point.

gmse_apply(res_mod = alt_res, obs_mod = observation, 
           man_mod = alt_man, use_mod = alt_usr, X = 1000);
## $resource_results
## [1] 1500
## 
## $observation_results
## [1] 1678.005
## 
## $manager_results
## [1] 678.0045
## 
## $user_results
## [1] 745.805

It is possible to, e.g., specify a simple resource and observation model, but then take advantage of the genetic algorithm to predict policy decisions and user actions (see Fisheries example integrating FLR for a fisheries example). This can be done by using the default GMSE manager and user functions (written below explicitly, though this is not necessary).

gmse_apply(res_mod = alt_res, obs_mod = alt_obs, 
           man_mod = manager, use_mod = user, X = 1000);
## $resource_results
## [1] 1500
## 
## $observation_results
## [1] 1350
## 
## $manager_results
##          resource_type scaring culling castration feeding help_offspring
## policy_1             1      NA      73         NA      NA             NA
## 
## $user_results
##         resource_type scaring culling castration feeding help_offspring
## Manager             1      NA       0         NA      NA             NA
## user_1              1      NA      13         NA      NA             NA
## user_2              1      NA      13         NA      NA             NA
## user_3              1      NA      13         NA      NA             NA
## user_4              1      NA      13         NA      NA             NA
##         tend_crops kill_crops
## Manager         NA         NA
## user_1          NA         NA
## user_2          NA         NA
## user_3          NA         NA
## user_4          NA         NA

Running GMSE simulations by looping gmse_apply

Instead of using the gmse function, multiple simulations of GMSE can be run by calling gmse_apply through a loop, reassigning outputs where necessary for the next generation. This is best accomplished using the argument old_list, which allows previous full results from gmse_apply to be reinserted into the gmse_apply function. The argument old_list is NULL by default, but can instead take the output of a previous full list return of gmse_apply. This old_list produced when get_res = Full includes all data structures and parameter values necessary for a unique simulation of GMSE. Note that custom functions sent to gmse_apply still need to be specified (res_mod, obs_mod, man_mod, and use_mod). An example of using get_res and old_list in tandem to loop gmse_apply is shown below.

to_scare  <- FALSE;
sim_old   <- gmse_apply(scaring = to_scare, get_res = "Full", stakeholders = 6);
sim_sum_1 <- matrix(data = NA, nrow = 20, ncol = 7);
for(time_step in 1:20){
    sim_new               <- gmse_apply(scaring = to_scare, get_res = "Full", 
                                        old_list = sim_old);
    sim_sum_1[time_step, 1] <- time_step;
    sim_sum_1[time_step, 2] <- sim_new$basic_output$resource_results[1];
    sim_sum_1[time_step, 3] <- sim_new$basic_output$observation_results[1];
    sim_sum_1[time_step, 4] <- sim_new$basic_output$manager_results[2];
    sim_sum_1[time_step, 5] <- sim_new$basic_output$manager_results[3];
    sim_sum_1[time_step, 6] <- sum(sim_new$basic_output$user_results[,2]); 
    sim_sum_1[time_step, 7] <- sum(sim_new$basic_output$user_results[,3]); 
    sim_old                 <- sim_new;
}
colnames(sim_sum_1) <- c("Time", "Pop_size", "Pop_est", "Scare_cost", 
                         "Cull_cost", "Scare_count", "Cull_count");
print(sim_sum_1);
##       Time Pop_size   Pop_est Scare_cost Cull_cost Scare_count Cull_count
##  [1,]    1     1148  929.7052         NA       110          NA         54
##  [2,]    2     1266 1224.4898         NA        32          NA        186
##  [3,]    3     1252 1111.1111         NA        63          NA         90
##  [4,]    4     1402 1405.8957         NA        17          NA        348
##  [5,]    5     1366 1179.1383         NA        39          NA        150
##  [6,]    6     1448 1541.9501         NA        13          NA        456
##  [7,]    7     1149 1337.8685         NA        21          NA        282
##  [8,]    8     1022  839.0023         NA       110          NA         54
##  [9,]    9     1154  952.3810         NA       110          NA         54
## [10,]   10     1359  884.3537         NA       110          NA         54
## [11,]   11     1576 1179.1383         NA        40          NA        150
## [12,]   12     1654 1496.5986         NA        14          NA        426
## [13,]   13     1495 1541.9501         NA        12          NA        498
## [14,]   14     1202 1292.5170         NA        24          NA        246
## [15,]   15     1169 1179.1383         NA        39          NA        150
## [16,]   16     1226 1201.8141         NA        34          NA        174
## [17,]   17     1245 1043.0839         NA       110          NA         54
## [18,]   18     1428 1405.8957         NA        17          NA        348
## [19,]   19     1262 1632.6531         NA        11          NA        540
## [20,]   20      857  748.2993         NA       109          NA         54

Note that one element of the full list gmse_apply output is the ‘basic_output’ itself, which is produced by default when get_res = "basic". This is what is being used to store the output of sim_new into sim_sum_1. Next, we show how the flexibility of gmse_apply can be used to dynamically redefine simulation conditions.

Changing simulation conditions using gmse_apply

We can take advantage of gmse_apply to dynamically change parameter values mid-loop. For example, below shows the same code used in the previous example, but with a policy of scaring introduced on time step 10.

to_scare  <- FALSE;
sim_old   <- gmse_apply(scaring = to_scare, get_res = "Full", stakeholders = 6);
sim_sum_2 <- matrix(data = NA, nrow = 20, ncol = 7);
for(time_step in 1:20){
    sim_new               <- gmse_apply(scaring = to_scare, get_res = "Full", 
                                        old_list = sim_old);
    sim_sum_2[time_step, 1] <- time_step;
    sim_sum_2[time_step, 2] <- sim_new$basic_output$resource_results[1];
    sim_sum_2[time_step, 3] <- sim_new$basic_output$observation_results[1];
    sim_sum_2[time_step, 4] <- sim_new$basic_output$manager_results[2];
    sim_sum_2[time_step, 5] <- sim_new$basic_output$manager_results[3];
    sim_sum_2[time_step, 6] <- sum(sim_new$basic_output$user_results[,2]); 
    sim_sum_2[time_step, 7] <- sum(sim_new$basic_output$user_results[,3]); 
    sim_old                 <- sim_new;
    if(time_step == 10){
        to_scare <- TRUE;
    }
}
colnames(sim_sum_2) <- c("Time", "Pop_size", "Pop_est", "Scare_cost", 
                         "Cull_cost", "Scare_count", "Cull_count");
print(sim_sum_2);
##       Time Pop_size   Pop_est Scare_cost Cull_cost Scare_count Cull_count
##  [1,]    1     1142 1133.7868         NA        53          NA        108
##  [2,]    2     1187 1065.7596         NA       102          NA         54
##  [3,]    3     1341 1043.0839         NA       109          NA         54
##  [4,]    4     1536 1541.9501         NA        13          NA        456
##  [5,]    5     1400 1224.4898         NA        31          NA        192
##  [6,]    6     1399 1224.4898         NA        31          NA        192
##  [7,]    7     1442 1269.8413         NA        26          NA        228
##  [8,]    8     1448 1451.2472         NA        15          NA        396
##  [9,]    9     1285 1360.5442         NA        19          NA        312
## [10,]   10     1174 1428.5714         NA        16          NA        372
## [11,]   11      983  929.7052         10       109           0         54
## [12,]   12     1104  861.6780         10       110           1         54
## [13,]   13     1248 1337.8685         59        21           0        282
## [14,]   14     1166 1224.4898         71        31           0        192
## [15,]   15     1151 1428.5714         84        16           0        372
## [16,]   16      951  816.3265         10       110           1         54
## [17,]   17     1051 1156.4626         34        45           0        132
## [18,]   18     1142 1065.7596         10       106           2         54
## [19,]   19     1295 1201.8141         45        34           0        174
## [20,]   20     1356 1111.1111         38        63           0         90

Hence, in addition to the previously explained benefits of the flexible gmse_apply function, one particularly useful feature is that we can use it to study change in policy availability – in the above case, what happens when scaring is suddenly introduced as a possible policy option. Similar things can be done, for example, to see how manager or user power changes over time. In the example below, users’ budgets increase by 100 every time step, with the manager’s budget remaining the same. The consequence of this increasing user budget is higher rates of culling and decreased population size.

ub          <- 500;
sim_old     <- gmse_apply(get_res = "Full", stakeholders = 6, user_budget = ub);
sim_sum_3   <- matrix(data = NA, nrow = 20, ncol = 6);
for(time_step in 1:20){
    sim_new               <- gmse_apply(get_res = "Full", old_list = sim_old,
                                        user_budget = ub);
    sim_sum_3[time_step, 1] <- time_step;
    sim_sum_3[time_step, 2] <- sim_new$basic_output$resource_results[1];
    sim_sum_3[time_step, 3] <- sim_new$basic_output$observation_results[1];
    sim_sum_3[time_step, 4] <- sim_new$basic_output$manager_results[3];
    sim_sum_3[time_step, 5] <- sum(sim_new$basic_output$user_results[,3]);
    sim_sum_3[time_step, 6] <- ub;
    sim_old                 <- sim_new;
    ub                      <- ub + 100;
}
colnames(sim_sum_3) <- c("Time", "Pop_size", "Pop_est", "Cull_cost", "Cull_count",
                         "User_budget");
print(sim_sum_3);
##       Time Pop_size  Pop_est Cull_cost Cull_count User_budget
##  [1,]    1     1169 1043.084        80         36         500
##  [2,]    2     1310 1156.463        23        156         600
##  [3,]    3     1332 1360.544        12        348         700
##  [4,]    4     1150 1065.760        74         60         800
##  [5,]    5     1410 1428.571        12        450         900
##  [6,]    6     1141 1111.111        57        102        1000
##  [7,]    7     1268 1360.544        19        342        1100
##  [8,]    8     1108 1043.084       110         60        1200
##  [9,]    9     1272  952.381       110         66        1300
## [10,]   10     1407 1405.896        21        396        1400
## [11,]   11     1206 1088.435       110         78        1500
## [12,]   12     1356 1133.787        76        126        1600
## [13,]   13     1499 1133.787        84        120        1700
## [14,]   14     1652 1746.032        16        672        1800
## [15,]   15     1174 1111.111       110        102        1900
## [16,]   16     1292 1020.408       110        108        2000
## [17,]   17     1452 1451.247        31        402        2100
## [18,]   18     1271 1133.787       109        120        2200
## [19,]   19     1381 1224.490        69        198        2300
## [20,]   20     1413 1292.517        55        258        2400

There is an important note to make about changing arguments to gmse_apply when old_list is being used: The function gmse_apply is trying to avoid a crash, so gmse_apply will accomodate parameter changes by rebuilding data structures if necessary. For example, if the number of stakeholders is changed (and by including an argument such as stakeholders to gmse_apply, it is assumed that stakeholders are changing even they are not), then a new array of agents will need to be built. If landscape dimensions are changed (or just include the argument land_dim_1 or land_dim_2), then a new landscape willl be built. For most simulation purposes, this will not introduce any undesirable effect on simulation results, but it should be noted and understood when developing models.

Special considerations for looping with custom sub-models

There are some special considerations that need to be made when using custom sub-models and the old_list argument within a loop as above. These considerations boil down to two key points.

  1. Custom sub-models always need to be read in explicitly as an argument in gmse_apply (i.e., they will not be remembered by old_list).
  2. Custom sub-model arguments also always need to be updated outside of gmse_apply before output is used as an argument in old_list (i.e., gmse_apply cannot know what custom function argument needs to be updated, so this needs to be done manually).

An example below illustrates the above points more clearly. Assume that the custom resource sub-model defined above needs to be integrated with the default observation, manager, and user sub-models using gmse_apply.

alt_res <- function(X, K = 2000, rate = 1){
    X_1 <- X + rate*X*(1 - X/K);
    return(X_1);
}

The sub-model can be integrated once using gmse_apply as demonstrated above, but in the full gmse_apply output, the argument X will not change from its initial value (because sub-model functions can take any number of arbitrary arguments, gmse_apply has no way of knowing that X is meant to be the resource number and not some other parameter).

sim_4 <- gmse_apply(res_mod = alt_res, X = 1000, get_res = "Full");
print(sim_4$basic_output);
## $resource_results
## [1] 1500
## 
## $observation_results
## [1] 1609.977
## 
## $manager_results
##          resource_type scaring culling castration feeding help_offspring
## policy_1             1      NA      77         NA      NA             NA
## 
## $user_results
##         resource_type scaring culling castration feeding help_offspring
## Manager             1      NA       0         NA      NA             NA
## user_1              1      NA      12         NA      NA             NA
## user_2              1      NA      12         NA      NA             NA
## user_3              1      NA      12         NA      NA             NA
## user_4              1      NA      12         NA      NA             NA
##         tend_crops kill_crops
## Manager         NA         NA
## user_1          NA         NA
## user_2          NA         NA
## user_3          NA         NA
## user_4          NA         NA

Note that in the above output, the resource abundance has increased and is now 1500. But if we look at sim_4$X, the value is still 1000.

print(sim_4$X);
## [1] 1000

To loop through multiple time steps with the custom function alt_res, it is therefore necessary to update sim4$X with the updated value from either sim4$resource_vector or sim4$basic_output$resource_results (the two values should be identical). The loop below shows a simple example.

init_abun   <- 1000;
sim_old     <- gmse_apply(get_res = "Full", res_mod = alt_res, X = init_abun);
for(time_step in 1:20){
    sim_new               <- gmse_apply(res_mod = alt_res, get_res = "Full", 
                                        old_list = sim_old);
    sim_old               <- sim_new;
    sim_old$X             <- sim_new$resource_vector;
}

Note again that the custom sub-model is read into to gmse_apply as an argument within the loop (res_mod = alt_res), and the output of sim_new is used to update the custom argument X in alt_res (sim_old$X <- sim_new$resource_vector). The population quickly increases to near carrying capacity, which can be summarised by using the same table structure explained above.

init_abun   <- 1000;
sim_old     <- gmse_apply(get_res = "Full", res_mod = alt_res, X = init_abun);
sim_sum_4   <- matrix(data = NA, nrow = 5, ncol = 5);
for(time_step in 1:5){
    sim_new                 <- gmse_apply(res_mod = alt_res, get_res = "Full", 
                                          old_list = sim_old);
    sim_sum_4[time_step, 1] <- time_step;
    sim_sum_4[time_step, 2] <- sim_new$basic_output$resource_results[1];
    sim_sum_4[time_step, 3] <- sim_new$basic_output$observation_results[1];
    sim_sum_4[time_step, 4] <- sim_new$basic_output$manager_results[3];
    sim_sum_4[time_step, 5] <- sum(sim_new$basic_output$user_results[,3]);
    sim_old                 <- sim_new;
    sim_old$X               <- sim_new$resource_vector;
}
colnames(sim_sum_4) <- c("Time", "Pop_size", "Pop_est", "Cull_cost", 
                         "Cull_count");
print(sim_sum_4);
##      Time Pop_size  Pop_est Cull_cost Cull_count
## [1,]    1     1500 1768.707        10        400
## [2,]    2     1875 1882.086        10        400
## [3,]    3     1992 1859.410        10        400
## [4,]    4     1999 2154.195        10        400
## [5,]    5     1999 2063.492        10        400

This is the recommended way to loop custom functions in gmse_apply. Note that elements of old_list will over-ride custom arguments to gmse_apply so specifying custom arguments that are already present in old_list will not work.

Replenishing crops after consumption

Unlike with the gmse function, gmse_apply does not automatically assume that crop production should be replenished after a single time step. The second layer of the landscape holds crop production on a cell. This will be depleted if resources consume crops on the landscape.

sim_consume <- gmse_apply(land_dim_1 = 8, land_dim_2 = 8, 
                          res_consume = 0.02, get_res = "Full");
print(round(sim_consume$LAND[,,2], digits = 2));
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
## [1,] 0.82 0.72 0.77 0.80 0.80 0.65 0.72 0.65
## [2,] 0.74 0.78 0.72 0.80 0.82 0.77 0.71 0.77
## [3,] 0.72 0.75 0.74 0.72 0.75 0.80 0.74 0.64
## [4,] 0.74 0.63 0.71 0.71 0.74 0.82 0.70 0.60
## [5,] 0.70 0.77 0.74 0.64 0.68 0.72 0.77 0.75
## [6,] 0.78 0.75 0.68 0.75 0.65 0.78 0.68 0.71
## [7,] 0.71 0.72 0.82 0.94 0.75 0.78 0.62 0.67
## [8,] 0.77 0.67 0.72 0.71 0.83 0.67 0.64 0.67

If we run this for five time steps using a loop in gmse_apply, then resources will continue to deplete crops on the landscape.

sim_old     <- gmse_apply(land_dim_1 = 8, land_dim_2 = 8, 
                          res_consume = 0.02, get_res = "Full");
for(time_step in 1:5){
    sim_new                 <- gmse_apply(get_res = "Full", old_list = sim_old);
    sim_sum_4[time_step, 1] <- time_step;
    sim_old                 <- sim_new;
}
print(round(sim_old$LAND[,,2], digits = 2));
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
## [1,] 0.12 0.12 0.15 0.13 0.15 0.16 0.14 0.09
## [2,] 0.13 0.23 0.14 0.14 0.14 0.17 0.10 0.12
## [3,] 0.14 0.16 0.12 0.17 0.14 0.10 0.15 0.20
## [4,] 0.18 0.14 0.15 0.16 0.21 0.13 0.12 0.16
## [5,] 0.22 0.16 0.14 0.14 0.12 0.18 0.14 0.15
## [6,] 0.14 0.14 0.16 0.17 0.10 0.14 0.09 0.11
## [7,] 0.17 0.11 0.10 0.16 0.14 0.20 0.16 0.13
## [8,] 0.16 0.10 0.15 0.22 0.20 0.12 0.18 0.16

Notice that the amount of crops on each cell has decreased substantially after five time steps. To replenish the crops after every repl time step, we can use the following code.

sim_old     <- gmse_apply(land_dim_1 = 8, land_dim_2 = 8, 
                          res_consume = 0.02, get_res = "Full");
repl        <- 1;
for(time_step in 1:5){
    sim_new                 <- gmse_apply(get_res = "Full", old_list = sim_old);
    sim_sum_4[time_step, 1] <- time_step;
    if(time_step %% repl == 0){ # If remainder of time_step / repl is zero
        sim_old$LAND[,,2] <- 1;
    }
    sim_old                 <- sim_new;
}
print(round(sim_old$LAND[,,2], digits = 2));
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
## [1,] 0.19 0.11 0.10 0.15 0.14 0.16 0.13 0.14
## [2,] 0.16 0.11 0.18 0.14 0.14 0.18 0.17 0.18
## [3,] 0.12 0.14 0.14 0.18 0.10 0.14 0.16 0.13
## [4,] 0.14 0.15 0.10 0.14 0.14 0.13 0.14 0.16
## [5,] 0.20 0.13 0.11 0.11 0.14 0.18 0.18 0.13
## [6,] 0.11 0.12 0.15 0.15 0.19 0.16 0.17 0.12
## [7,] 0.19 0.15 0.16 0.15 0.13 0.11 0.23 0.14
## [8,] 0.15 0.13 0.11 0.12 0.17 0.22 0.11 0.18

Now with the crop on the landscape replenishing every time step, consumption after five time steps comparable to consumption after a single time step. This is likely to be critical if simulating resources that must consume a certain amount to survive (consume_surv > 0) or reproduce (consume_repr > 0), and gmse_apply thereby provides some flexibility in terms of how frequently (and how much) landscape values change from one time step to the next.