diff --git a/.gitignore b/.gitignore
index 63e30d621fc768df0bf871b42d9e3a3d66afe428..a76188c7c4afe73a614577fa1fe5531d3a334b74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,8 @@ cmake-build-debug
 autodocs
 test-data
 removed
+<<<<<<< HEAD
 build-windows
+=======
+cmake-build-debug-system/
+>>>>>>> origin/butterflies
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d671d9600b4b9e67e7b3e49bd2be5456cb94f548..05993bf744095920ea251a6b776c119aaf8a45da 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,7 +16,7 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUA
             -DHAVE_INLINE
             -DGSL_C99_INLINE
             -Wno-unused-function)
-endif()
+endif ()
 
 if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
     add_compile_options(-Wno-stringop-truncation -Wno-stringop-overflow)
@@ -27,20 +27,18 @@ elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "Intel")
 elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
     add_compile_options("/std:c17")
     link_libraries(getopt)
-else()
+else ()
     MESSAGE("Unknown compiler: ${CMAKE_C_COMPILER_ID}")
-endif()
+endif ()
 
 set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
+set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
 
 if (WIN32 AND NOT MSVC)
     add_compile_options(-D__USE_MINGW_ANSI_STDIO=1)
 endif ()
 
 
-
-
-
 execute_process(
         WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
         ERROR_QUIET
@@ -51,18 +49,18 @@ execute_process(
 )
 
 if ("${CATS_GIT_VERSION}" STREQUAL "")
-execute_process(
-        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
-        ERROR_QUIET
-        OUTPUT_STRIP_TRAILING_WHITESPACE
-        OUTPUT_VARIABLE CATS_GIT_VERSION
-        COMMAND git log -1 --format=%h
-)
+    execute_process(
+            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+            ERROR_QUIET
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+            OUTPUT_VARIABLE CATS_GIT_VERSION
+            COMMAND git log -1 --format=%h
+    )
 
-endif()
+endif ()
 if ("${CATS_GIT_VERSION}" STREQUAL "")
     set(CATS_GIT_VERSION UNKNOWN)
-endif()
+endif ()
 
 
 add_definitions("-DCATS_GIT_VERSION=${CATS_GIT_VERSION}")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 334dac1cf7d5a79819550fbce2f0561c27f82fe5..2bf1b6ad8c63fa00570e3adfeff8cf18fcb32a36 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,3 +6,4 @@ add_subdirectory(cats_strings)
 add_subdirectory(cats_ini)
 add_subdirectory(cats_csv)
 add_subdirectory(modules/cats_test_module)
+add_subdirectory(modules/butterflies)
\ No newline at end of file
diff --git a/src/cats/CMakeLists.txt b/src/cats/CMakeLists.txt
index a2c84bc7ae9ebf8a4bec0c5e1bc5476fe282d7f8..00239ae79b9860e9a72cbc61f3233041b377a531 100644
--- a/src/cats/CMakeLists.txt
+++ b/src/cats/CMakeLists.txt
@@ -39,7 +39,7 @@ include_directories(${GDAL_INCLUDE_DIR})
 include_directories(${GSL_INCLUDE_DIR})
 include_directories(${MPI_C_INCLUDE_PATH})
 
-add_library(libcats STATIC "" stats/statistics.c stats/statistics.h data/cats_global.c ../cats_windows.h test/test_ini.c test/test_ini.h debug/debug_vital_rates.c debug/debug_vital_rates.h)
+add_library(libcats STATIC "" stats/statistics.c stats/statistics.h data/cats_global.c ../cats_windows.h test/test_ini.c test/test_ini.h debug/debug_vital_rates.c debug/debug_vital_rates.h overlays/overlay_resources.c overlays/overlay_resources.h paths/path_patterns.c paths/path_patterns.h)
 target_include_directories(libcats PUBLIC ".")
 
 target_sources(libcats PRIVATE
diff --git a/src/cats/actions/action_overlay_update.c b/src/cats/actions/action_overlay_update.c
index da4fd0a475523ad4b4000c6bd15c81c01e766739..a6ad6e2aaeca67cafc8df38dee230299f124cf19 100644
--- a/src/cats/actions/action_overlay_update.c
+++ b/src/cats/actions/action_overlay_update.c
@@ -30,6 +30,7 @@
 #include "grids/cats_grid.h"
 #include "overlays/overlay_exclusion.h"
 #include "actions/cats_actions.h"
+#include "paths/path_patterns.h"
 
 bool overlay_update_needed(struct cats_configuration *conf, int32_t time, enum overlay_type type);
 
@@ -44,10 +45,12 @@ enum action_status action_overlay_update(struct cats_grid *grid, struct cats_con
 
         const int32_t year = conf->time.year_current;
 
+
+
         for (enum overlay_type type = OL_NONE; type < OL_MAX; type++) {
                 if (type == OL_NONE) continue;
 
-                if (conf->overlays.enabled[type]) {
+                if (conf->overlays.overlay[type].enabled) {
                         bool updated = update_overlay_if_needed(conf, year, type);
 
                         if (type == OL_EXCLUSION && updated == true) {
@@ -62,8 +65,8 @@ enum action_status action_overlay_update(struct cats_grid *grid, struct cats_con
 
 bool overlay_update_needed(struct cats_configuration *conf, int32_t time, enum overlay_type type)
 {
-        if (!conf->overlays.enabled[type]) return false;
-        int reload_interval = conf->overlays.reload_interval[type];
+        if (!conf->overlays.overlay[type].enabled) return false;
+        int reload_interval = conf->overlays.overlay[type].reload_interval;
 
         switch (type) {
                 case OL_EXCLUSION:
@@ -72,6 +75,9 @@ bool overlay_update_needed(struct cats_configuration *conf, int32_t time, enum o
                 case OL_HABITAT_TYPE_CC:
                         if (conf->time.phase != PHASE_SIMULATION && conf->overlays.habitat_cc == NULL) return true;
                         break;
+                case OL_RESOURCE:
+                        if (conf->time.phase != PHASE_SIMULATION && conf->overlays.resources == NULL) return true;
+                        break;
                 case OL_MAX:
                 case OL_NONE:
                         return false;
@@ -104,14 +110,13 @@ bool overlay_update_needed(struct cats_configuration *conf, int32_t time, enum o
 char *get_overlay_filename(struct cats_configuration *conf, enum overlay_type type, int32_t time)
 {
         assert(conf != NULL);
-        char *pattern = conf->overlays.filename[type];
-        if (conf->overlays.is_static[type]) {
+        char *pattern = conf->overlays.overlay[type].filename;
+        if (conf->overlays.overlay[type].is_static) {
                 return strdup(pattern);
         }
 
-        char *time_string = formatted_year(&conf->time, time);
-        char *result = replace_substring(pattern, "%Y", time_string);
-        free(time_string);
+        char *result = filename_pattern_substitution(pattern, conf, time);
+
         return result;
 
 }
@@ -120,17 +125,17 @@ char *get_overlay_filename(struct cats_configuration *conf, enum overlay_type ty
 bool update_overlay_if_needed(struct cats_configuration *conf, int32_t time, enum overlay_type type)
 {
         if (overlay_update_needed(conf, time, type) == false) {
-                log_message(LOG_INFO, "OVRL: reusing old overlay for overlay '%s'", conf->overlays.name[type]);
+                log_message(LOG_INFO, "OVRL: reusing old overlay for overlay '%s'", conf->overlays.overlay[type].name);
                 return false;
         }
 
 
         char *overlay_filename = get_overlay_filename(conf, type, time);
-        log_message(LOG_INFO, "OVRL: overlay '%s': loading from file '%s'", conf->overlays.name[type],
+        log_message(LOG_INFO, "OVRL: overlay '%s': loading from file '%s'", conf->overlays.overlay[type].name,
                     overlay_filename);
         load_overlay_from_file(conf, type, overlay_filename);
 
-        log_message(LOG_INFO, "OVRL: overlay '%s': loaded from file '%s'", conf->overlays.name[type], overlay_filename);
+        log_message(LOG_INFO, "OVRL: overlay '%s': loaded from file '%s'", conf->overlays.overlay[type].name, overlay_filename);
         free(overlay_filename);
 
 
diff --git a/src/cats/actions/action_population_grid_output.c b/src/cats/actions/action_population_grid_output.c
index 790ec0faa376d8e41c7aa66ce7075c6f85473129..323793b62790da15249d93c4dd50b2e586939074 100644
--- a/src/cats/actions/action_population_grid_output.c
+++ b/src/cats/actions/action_population_grid_output.c
@@ -53,7 +53,7 @@ enum action_status action_save_adults(struct cats_grid *grid, struct cats_config
 
         save_population_to_gdal(grid, conf);
 
-        if (conf->write_all) {
+        if (conf->output.write_all) {
                 save_juveniles_to_gdal(grid, conf);
                 save_seeds_to_gdal(grid, conf);
         }
diff --git a/src/cats/actions/process_inter_period_survival.c b/src/cats/actions/process_inter_period_survival.c
index cf10c16cc8c17c8c9f4e08ee3ac5949469121e54..479385a710b9537fb919d1156d0de5ce54e15fdb 100644
--- a/src/cats/actions/process_inter_period_survival.c
+++ b/src/cats/actions/process_inter_period_survival.c
@@ -88,14 +88,14 @@ enum action_status inter_period_survival_action(struct cats_grid *grid, struct c
 
 
         if (conf->command_line_options.lambda_test) {
-                write_stats(grid, conf, conf->lambda_stat_file, "pre-trans");
+                write_stats(grid, conf, conf->output.lambda_stat_file, "pre-trans");
         }
 
 
         threaded_action(&area_inter_period_survival, grid, conf, TS_DEFAULT);
 
         if (conf->command_line_options.lambda_test) {
-                write_stats(grid, conf, conf->lambda_stat_file, "post-trans");
+                write_stats(grid, conf, conf->output.lambda_stat_file, "post-trans");
         }
 
 
diff --git a/src/cats/actions/process_seed_dispersal.c b/src/cats/actions/process_seed_dispersal.c
index 6f106be8780ec9eba814ee461f936b68c1f263d6..8157c95b09fcf74f17d9d72cbca5e0bdac6cf45c 100644
--- a/src/cats/actions/process_seed_dispersal.c
+++ b/src/cats/actions/process_seed_dispersal.c
@@ -131,7 +131,7 @@ enum action_status process_disperse_seeds(struct cats_grid *grid, struct cats_co
         }
 #endif
 
-        if (conf->write_all && is_output_year(&conf->time)) {
+        if (conf->output.write_all && is_output_year(&conf->time)) {
                 save_dispersed_seeds_to_gdal(grid, conf);
         }
 
diff --git a/src/cats/actions/setup.c b/src/cats/actions/setup.c
index 9b01c39d39bcf54f83d243c44c1ae89e5f8f6aff..d88079d3b9f4f5fe416fee6c3f9d6d6b7c425dc1 100644
--- a/src/cats/actions/setup.c
+++ b/src/cats/actions/setup.c
@@ -68,8 +68,8 @@ void setup_lambda_test_simulation(struct cats_configuration *conf, struct cats_g
         }
 
         conf->lambda_burn_in_time = conf->time.simulation_length / 5;
-        conf->lambda_stat_file = create_and_initialize_lambda_stat_file(conf, NULL, grid);
-        write_stats(grid, conf, conf->lambda_stat_file, "initial");
+        conf->output.lambda_stat_file = create_and_initialize_lambda_stat_file(conf, NULL, grid);
+        write_stats(grid, conf, conf->output.lambda_stat_file, "initial");
 
 }
 
@@ -123,8 +123,8 @@ void setup_lambda_gradient_simulation(struct cats_configuration *conf, struct ca
         }
 
         conf->lambda_burn_in_time = conf->time.simulation_length / 5;
-        conf->lambda_stat_file = create_and_initialize_lambda_stat_file(conf, "gradient", grid);
-        write_stats(grid, conf, conf->lambda_stat_file, "initial");
+        conf->output.lambda_stat_file = create_and_initialize_lambda_stat_file(conf, "gradient", grid);
+        write_stats(grid, conf, conf->output.lambda_stat_file, "initial");
 
 }
 
diff --git a/src/cats/actions/setup_actions.c b/src/cats/actions/setup_actions.c
index 1c03f487fcc98b0dbbaf28132177801f26e7aa3c..6c2dcd63284dd14c92b6c041a2ec9756f8b61532 100644
--- a/src/cats/actions/setup_actions.c
+++ b/src/cats/actions/setup_actions.c
@@ -160,7 +160,9 @@ append_action(struct cats_configuration *conf, action_function action, enum cats
 
 void list_actions_full(struct cats_configuration *conf)
 {
-        log_message(LOG_INFO, "Action functions: %d", conf->action_functions.count);
+
+        log_message(LOG_INFO, "Number of registered action functions: %d", conf->action_functions.count);
+
         for (int32_t i = 0; i < conf->action_functions.count; i++) {
                 log_message(LOG_INFO, "%d: %s %p ", i, conf->action_functions.names[i],
                             conf->action_functions.functions[i]);
diff --git a/src/cats/cats.c b/src/cats/cats.c
index 9cbc75a925df860a35ab8e5cf8adb37a3d5a9bb0..202540723965191f9a49fdfe6c225c098d9925a9 100644
--- a/src/cats/cats.c
+++ b/src/cats/cats.c
@@ -131,8 +131,10 @@ void load_modules(struct cats_configuration *conf)
 {
         for (int32_t i = 0; i < conf->modules.found_count; i++) {
                 int32_t id = load_module(conf, &conf->modules.module[i]);
-                log_message(LOG_RAW, "%s: %d/%d-> %d (%d)\n", conf->modules.module[i].name, i, conf->modules.count, id,
-                       conf->modules.module[i].flags);
+
+                log_message(LOG_IMPORTANT, "Loaded module %d of %d: '%s' with id %d and flags %d",
+                            i + 1, conf->modules.count, conf->modules.module[i].name, id, conf->modules.module[i].flags);
+
         }
 
         for (int32_t i = 0; i < conf->param_count; i++) {
@@ -163,8 +165,8 @@ void load_modules(struct cats_configuration *conf)
 
                         }
 
+                        log_message(LOG_INFO, "%s: Loading vital rate functions for module id %d", __func__, j);
 
-                                printf("%s: %d\n", __func__, j);
                                 if (p->module_data[j].load_species_param_function) {
                                         p->module_data[j].load_species_param_function(conf, conf->ini, p->species_config_section, p);
                                 }
@@ -188,8 +190,9 @@ struct cats_configuration *load_main_configuration(const struct program_options
 
 
         load_modules(conf);
+        setup_module_directories(conf);
+
 
-        print_modules(conf);
         setup_simulation_actions(conf);
         // which values of the configuration file did we ignore?
         return conf;
diff --git a/src/cats/cats_global.h b/src/cats/cats_global.h
index 1795b433c170f28a84e05a22e432899d6dabe49f..cc7a794edd752308d9cc0cce1078686bcdc1b6a4 100644
--- a/src/cats/cats_global.h
+++ b/src/cats/cats_global.h
@@ -98,6 +98,9 @@ struct cats_debug_options {
         FILE *misc_debug_file; /// @brief global variable: file handle for a debug file documenting a single cells
 };
 
-
+static void abort_implemented(const char *name) {
+        fprintf(stderr, "function '%s' is not yet implemented\n", name);
+        exit_cats(EXIT_FAILURE);
+}
 
 #endif //CATS_GLOBAL_H_
diff --git a/src/cats/command_line/command_line_options.c b/src/cats/command_line/command_line_options.c
index 460bc247f936ec5db3ac05ce8745b9d0c4c0920f..71199d3de5f53854188f210cf2d3902a2bb9bef7 100644
--- a/src/cats/command_line/command_line_options.c
+++ b/src/cats/command_line/command_line_options.c
@@ -40,8 +40,11 @@ static struct option longopts[] = {
         {OPTION_SCALE_LAMBDA_ONLY_NAME,        no_argument,       NULL, OPTION_SCALE_LAMBDA_ONLY},
         {OPTION_SCALE_LAMBDA_TEST_NAME,        no_argument,       NULL, OPTION_SCALE_LAMBDA_TEST},
         {OPTION_SCALE_GRADIENT_NAME,           no_argument,       NULL, OPTION_SCALE_GRADIENT},
+
         {OPTION_LOG_FILE_NAME,                 required_argument, NULL, OPTION_LOG_FILE},
         {OPTION_SUMMARY_FILE_NAME,             required_argument, NULL, OPTION_SUMMARY_FILE},
+
+
         {OPTION_JSON_NAME,                     no_argument,       NULL, OPTION_JSON},
         {OPTION_DEBUG_MPI_NAME,                no_argument,       NULL, OPTION_DEBUG_MPI},
         {OPTION_SCALE_LAMBDA_CC_NAME,          required_argument, NULL, OPTION_SCALE_LAMBDA_CC},
@@ -51,9 +54,11 @@ static struct option longopts[] = {
         {OPTION_DEBUG_CONFIG_UNSPECIFIED_NAME, no_argument,       NULL, OPTION_DEBUG_CONFIG_UNSPECIFIED},
         {OPTION_DEBUG_LAMBDA_NAME,             no_argument,       NULL, OPTION_DEBUG_LAMBDA},
         {OPTION_DEBUG_VR_NAME,                 no_argument,       NULL, OPTION_DEBUG_VR},
+
         {OPTION_DEBUG_VR_SCALE_NAME,           required_argument, NULL, OPTION_DEBUG_VR_SCALE},
         {OPTION_DEBUG_VR_SUBDIVISIONS_NAME,    required_argument, NULL, OPTION_DEBUG_VR_SUBDIVISIONS},
         {OPTION_DEBUG_VR_OT_NAME,              required_argument, NULL, OPTION_DEBUG_VR_OT},
+
         {OPTION_POISSON_DAMPENING_NAME,        required_argument, NULL, OPTION_POISSON_DAMPENING},
         {OPTION_POISSON_DAMPENING_MIN_NAME,    required_argument, NULL, OPTION_POISSON_DAMPENING_MIN},
         {OPTION_POISSON_MAXIMUM_NAME,          required_argument, NULL, OPTION_POISSON_MAXIMUM},
diff --git a/src/cats/command_line/command_line_options.h b/src/cats/command_line/command_line_options.h
index 11634153c000cacef16e2452607fde5a2d8646c9..a5ed7b70bdd3fad7aea8ebba22209e90a4c7ae51 100644
--- a/src/cats/command_line/command_line_options.h
+++ b/src/cats/command_line/command_line_options.h
@@ -30,6 +30,7 @@
 #include "logging/logging.h"
 #include "cats_global.h"
 
+
 #define OPTION_LOG_FILE 1111
 #define OPTION_LOG_FILE_NAME "log-file"
 
diff --git a/src/cats/configuration/check_configuration.c b/src/cats/configuration/check_configuration.c
index f3dae83058dc9d89cef60e62ec83ab09c21a0656..4eaae98ecaf30719d2db216b7da409c5473269d9 100644
--- a/src/cats/configuration/check_configuration.c
+++ b/src/cats/configuration/check_configuration.c
@@ -64,7 +64,8 @@ int rate_failed(cats_dt_rates value, char *name)
 int range_float_check_at_least(cats_dt_rates value, cats_dt_rates larger_than, char *name)
 {
         if (value < larger_than) {
-                log_message(LOG_ERROR, "%s out of range: is %f, has to be >= %f", name, (double) value, (double) larger_than);
+
+                log_message(LOG_ERROR, "%s out of range: is %Lf, has to be >= %Lf", name, value, larger_than);
                 return 1;
         }
 
diff --git a/src/cats/configuration/configuration.c b/src/cats/configuration/configuration.c
index 35ed78474e11b09b46374bb3b60325d8b597c3e9..6d6fb2af6824235e861c56bdd6f93df2d5b257e6 100644
--- a/src/cats/configuration/configuration.c
+++ b/src/cats/configuration/configuration.c
@@ -64,7 +64,7 @@ void init_cats_configuration(struct cats_configuration *conf)
                 log_message(LOG_ERROR, "conf is NULL in %s", __func__);
                 exit(EXIT_FAILURE);
         }
-
+        init_stats_registry(&conf->stats_registry);
         init_module_registry(&conf->modules);
         reset_overlay(&conf->overlays);
         zero_statistics_stats(&conf->global_stats);
@@ -76,13 +76,21 @@ void init_cats_configuration(struct cats_configuration *conf)
         conf->cycle.action_counter = 0;
 
         conf->environment.count = 0;
-        conf->output_directory = strdup(DEFAULT_OUTPUT_DIRECTORY);
+        conf->output.output_directory = strdup(DEFAULT_OUTPUT_DIRECTORY);
         conf->stats_populated_threshold = DEFAULT_POPULATED_THRESHOLD;
 
-        conf->param_max_threads = -1;
-        conf->performance_file = NULL;
+        conf->param_max_threads = 1;
+        conf->output.performance_file = NULL;
+
         conf->grid_count = 1;
 
+        conf->overlays.custom_overlay_count = 0;
+        conf->overlays.custom_overlays = NULL;
+        conf->overlays.registered_custom_overlay_names = new_string_array();
+        conf->output.needed_output_directories = new_string_array();
+        conf->output.needed_module_output_directories = new_string_array();
+        conf->output.have_glm = false;
+        conf->output.write_all = false;
 
 #ifdef USEMPI
         conf->mpi.world_size = 1;
@@ -171,13 +179,13 @@ void post_process_configuration(struct cats_configuration *conf)
                 char *filename = malloc_or_die(count);
                 snprintf(filename, count, "performance/%s_%03d.csv", conf->run_name, conf->simulation.replicate);
 
-                conf->performance_file = fopen(filename, "w");
-                if (!conf->performance_file) {
+                conf->output.performance_file = fopen(filename, "w");
+                if (!conf->output.performance_file) {
                         log_message(LOG_ERROR, "failed to open %s: %s", filename, strerror(errno));
                         exit_cats(EXIT_FAILURE);
                 }
 
-                fprintf(conf->performance_file, "%s\n", "year,stat,cpu_time,wall_time");
+                fprintf(conf->output.performance_file, "%s\n", "year,stat,cpu_time,wall_time");
         }
 
         if (conf->param_max_threads <= 0) {
@@ -222,14 +230,14 @@ void cleanup_configuration(struct cats_configuration **conf_orig)
         struct cats_configuration *conf = *conf_orig;
         // THIS HAS TO BE THE FIRST ENTRY
         cleanup_module_registry(&conf->modules);
-        if (conf->summary_file) {
-                fflush(conf->summary_file);
-                fclose(conf->summary_file);
-                free(conf->summary_file_name);
+
+        if (conf->output.summary_file) {
+                fclose(conf->output.summary_file);
+                free(conf->output.summary_file_name);
         }
 
-        if (conf->statsfile_global) fclose(conf->statsfile_global);
-        free(conf->file_content);
+        if (conf->output.statsfile_global) fclose(conf->output.statsfile_global);
+        free(conf->output.file_content);
 
         /* keep sorted!*/
         free(conf->cycle.actions);
@@ -253,7 +261,7 @@ void cleanup_configuration(struct cats_configuration **conf_orig)
 
         free(conf->dispersal);
 
-        free(conf->output_directory);
+        free(conf->output.output_directory);
 
         for (int i = 0; i < conf->param_count; i++) {
                 free(conf->param[i].preset_name);
@@ -279,8 +287,8 @@ void cleanup_configuration(struct cats_configuration **conf_orig)
         //free(conf->predictors);
         cleanup_overlays(&conf->overlays);
         cleanup_environment_registry(&conf->environment_registry);
-        if (conf->performance_file) fclose(conf->performance_file);
-        conf->performance_file = NULL;
+        if (conf->output.performance_file) fclose(conf->output.performance_file);
+        conf->output.performance_file = NULL;
         free_ini(conf->ini);
 
 #ifdef USEMPI
@@ -291,7 +299,8 @@ void cleanup_configuration(struct cats_configuration **conf_orig)
         free(conf->command_line_options.output_directory);
 
         free(conf->global_stats.populated_by_classes);
-
+        free_string_array(&conf->output.needed_output_directories);
+        free_string_array(&conf->output.needed_module_output_directories);
         free(conf->param);
         conf->param = NULL;
         free(conf->run_name);
diff --git a/src/cats/configuration/configuration.h b/src/cats/configuration/configuration.h
index 15542b32603c58d418b5d51d210939d9ec800738..ca57b76b3f31e187ff92afa84b2f60f19713f332 100644
--- a/src/cats/configuration/configuration.h
+++ b/src/cats/configuration/configuration.h
@@ -122,19 +122,35 @@ struct cats_environment_collection {
 };
 
 
+struct cats_output {
+    struct string_array *needed_output_directories;
+    struct string_array *needed_module_output_directories;
+    bool have_glm;
+    bool write_all;
+    char *output_directory;
+    char *summary_file_name;
+    FILE *summary_file;
+    char *log_file_name;
+    FILE *lambda_stat_file;
+    bool param_compression;
+    FILE *statsfile_global;
+    FILE *performance_file;
+    char *file_content;
+
+};
+
 /// @brief Contains the configuration and state of the simulation
 struct cats_configuration {
 
-        char *output_directory;
-        char *summary_file_name;
-        FILE *summary_file;
-        char *log_file_name;
+
         bool quiet;
         struct cats_global *global;
         struct program_options command_line_options;
         bool debug_enabled;
         struct cats_ini *ini;
 
+        struct cats_stats_registry stats_registry;
+
         // cats_stats
         cats_dt_population stats_populated_threshold;
 
@@ -149,7 +165,6 @@ struct cats_configuration {
         int link_count;
 
 
-        bool write_all;
 
 
         bool all_species_same;
@@ -177,7 +192,7 @@ struct cats_configuration {
 
         struct cats_overlays overlays;
 
-        bool param_compression;
+
 
         int32_t grid_count;
 
@@ -195,7 +210,6 @@ struct cats_configuration {
         char *run_name;
 
         // actions
-        FILE *lambda_stat_file;
 
 
         //enum simulation_mode mode;
@@ -205,8 +219,7 @@ struct cats_configuration {
 
         int64_t rng_seed;
 
-        FILE *statsfile_global;
-        FILE *performance_file;
+
         bool timing_info;
 
         struct cats_cycle cycle;
@@ -216,8 +229,11 @@ struct cats_configuration {
 
         cats_dt_rates direct_scale_factor;
         cats_dt_rates lambda_at_OT; // FIXME, per parameter set
-        char *file_content;
+
         enum cats_debug_flags debug;
+
+
+        struct cats_output output;
 };
 
 void cleanup_configuration(struct cats_configuration **conf);
diff --git a/src/cats/configuration/load_configuration.c b/src/cats/configuration/load_configuration.c
index 9f6bfeca849d5c38a3eb179f969412245873c148..8fa69006c97af31901e61a2ae52c88625f6caf04 100644
--- a/src/cats/configuration/load_configuration.c
+++ b/src/cats/configuration/load_configuration.c
@@ -67,14 +67,16 @@ load_configuration_from_file(const char *filename, const struct program_options
         // create empty configuration
         struct cats_configuration *conf = new_configuration();
         conf->quiet = command_line_options->quiet;
-        conf->log_file_name = command_line_options->log_file;
-        conf->summary_file = NULL;
-        conf->summary_file_name = command_line_options->summary_file;
-        if (conf->summary_file_name) {
-                conf->summary_file = fopen(conf->summary_file_name, "w");
-                ENSURE_FILE_OPENED(conf->summary_file, conf->summary_file_name)
-                fprintf(conf->summary_file, "run.name,replicate,year,file.type,file.name\n");
-                fflush(conf->summary_file);
+
+        conf->output.log_file_name = command_line_options->log_file;
+        conf->output.summary_file = NULL;
+        conf->output.summary_file_name = command_line_options->summary_file;
+        if (conf->output.summary_file_name) {
+                conf->output.summary_file = fopen(conf->output.summary_file_name, "w");
+                ENSURE_FILE_OPENED(conf->output.summary_file, conf->output.summary_file_name)
+                fprintf(conf->output.summary_file, "type,path,year\n");
+                if (conf->output.log_file_name) fprintf(conf->output.summary_file, "log,%s,\n",conf->output.log_file_name);
+
         }
 #ifdef USEMPI
         mpi_setup(conf);
@@ -120,7 +122,7 @@ load_configuration_from_file(const char *filename, const struct program_options
         conf->ini = ini;
         // we keep a copy of the configuration file contents in memory
         if (conf->command_line_options.need_conf) {
-                conf->file_content = slurp_file(filename);
+                conf->output.file_content = slurp_file(filename);
         }
 
         // load data from loaded ini file into the configuration
@@ -135,10 +137,6 @@ load_configuration_from_file(const char *filename, const struct program_options
 
         // clean up and return
         free(count.dispersals);
-        if (conf->log_file_name && conf->summary_file) {
-                fprintf(conf->summary_file, "%s,%d,NA,log,%s\n", conf->run_name, conf->simulation.replicate, conf->log_file_name);
-                fflush(conf->summary_file);
-        }
         return conf;
 }
 
diff --git a/src/cats/configuration/load_configuration_general.c b/src/cats/configuration/load_configuration_general.c
index 2c83421d0bd167f779421fec9e5b815e56fcd147..5b044dc92bb1eed89b518427e11d08244f3d0360 100644
--- a/src/cats/configuration/load_configuration_general.c
+++ b/src/cats/configuration/load_configuration_general.c
@@ -63,14 +63,14 @@ void load_general_info(struct cats_configuration *conf, struct cats_ini *ini)
         char *od;
         bool have_od = load_conf_value(false, ini, "general", "output directory", &od);
         if (have_od) {
-                free(conf->output_directory);
-                conf->output_directory = od;
+                free(conf->output.output_directory);
+                conf->output.output_directory = od;
         }
 
         load_conf_value(false, ini, "general", "debug", &conf->debug_enabled);
-        load_conf_value(false, ini, "general", "compression", &conf->param_compression);
+        load_conf_value(false, ini, "general", "compression", &conf->output.param_compression);
 
-        load_conf_value(false, ini, "general", "write all", &conf->write_all);
+        load_conf_value(false, ini, "general", "write all", &conf->output.write_all);
         load_conf_value(false, ini, "general", "file format", &conf->geometry.file_format);
         load_conf_value(false, ini, "general", "ignore raster projection", &conf->geometry.ignore_raster_projection);
         load_conf_value(false, ini, "general", "threads", &conf->param_max_threads);
diff --git a/src/cats/configuration/load_configuration_glm.c b/src/cats/configuration/load_configuration_glm.c
index e3b80973512cb3f9d6a6f03c5fe493bca8b88442..366749270f35673dabefdf79b0faee9a217bf2ac 100644
--- a/src/cats/configuration/load_configuration_glm.c
+++ b/src/cats/configuration/load_configuration_glm.c
@@ -167,5 +167,7 @@ void add_glm_from_conf(struct cats_configuration *conf, struct cats_ini *ini, ch
                                                                                       predictors->string[i]);
                 add_to_environment(set, env);
         }
+
+        conf->output.have_glm = true;
 }
 
diff --git a/src/cats/configuration/load_configuration_overlays.c b/src/cats/configuration/load_configuration_overlays.c
index 3db90fc434579cc79fee970f8f78a512a2043d3b..bbacc6f7e1b1828624bd3efdbb463d9cb2e68028 100644
--- a/src/cats/configuration/load_configuration_overlays.c
+++ b/src/cats/configuration/load_configuration_overlays.c
@@ -52,25 +52,34 @@ void load_overlay_configuration(struct cats_configuration *conf, struct cats_ini
                 char *type_name = NULL;
 
                 load_conf_value(true, ini, overlay_section, "type", &type_name);
-                enum overlay_type type = get_overlay_type_from_name(type_name);
-                free(type_name);
+                enum overlay_type type = get_overlay_type_from_name(type_name, conf->overlays.registered_custom_overlay_names);
+
+                struct cats_overlay *overlay = NULL;
+
+                if (type < OL_CUSTOM && type > OL_NONE) {
+                        overlay = &conf->overlays.overlay[type];
+                } else if (type == OL_CUSTOM) {
+                        overlay = add_overlay(conf, type_name);
+                }
 
-                conf->overlays.enabled[type] = true;
 
 
-                load_conf_value(true, ini, overlay_section, "filename pattern", &conf->overlays.filename[type]);
-                load_conf_value(false, ini, overlay_section, "reload interval", &conf->overlays.reload_interval[type]);
+                overlay->enabled = true;
 
-                if (conf->overlays.reload_interval[type] > 0) {
-                        conf->overlays.is_static[type] = false;
+
+                load_conf_value(true, ini, overlay_section, "filename pattern", &overlay->filename);
+                load_conf_value(false, ini, overlay_section, "reload interval", &overlay->reload_interval);
+
+                if (overlay->reload_interval > 0) {
+                        overlay->is_static = false;
                 } else {
-                        conf->overlays.is_static[type] = true;
+                        overlay->is_static = true;
                 }
 
                 conf->overlays.have_overlays = true;
 
                 long double default_value = NAN;
-                conf->overlays.name[type] = strdup(overlay_name);
+                overlay->name = strdup(overlay_name);
 
                 switch (type) {
                         case OL_EXCLUSION:
@@ -78,18 +87,21 @@ void load_overlay_configuration(struct cats_configuration *conf, struct cats_ini
                         case OL_HABITAT_TYPE_CC:
 
                                 load_conf_value(true, ini, overlay_section, "table filename",
-                                                &conf->overlays.filename_aux[type]);
+                                                &overlay->filename_aux);
                                 load_conf_value(true, ini, overlay_section, "default value", &default_value);
-                                conf->overlays.aux_data[OL_HABITAT_TYPE_CC] = load_habitat_layer_cc_aux(
-                                        conf->overlays.filename_aux[type],
+                                overlay->aux_data = load_habitat_layer_cc_aux(
+                                        overlay->filename_aux,
                                         (double) default_value);
                                 break;
+                        case OL_RESOURCE:
+                                break;
                         case OL_NONE:
                         case OL_MAX:
-                        default:
+                        case OL_CUSTOM:
                                 log_message(LOG_ERROR, "unknown or unhandled overlay type '%s'", overlay_name);
                                 exit(EXIT_FAILURE);
                 }
+                free(type_name);
         }
 
         free_string_array(&overlay_names);
diff --git a/src/cats/configuration/load_configuration_species_params.c b/src/cats/configuration/load_configuration_species_params.c
index 8bd5ec5c752c5216b1a210d143e78ada9fefc810..5ac0ad001137a7377982d5aca0fa531f7aff834b 100644
--- a/src/cats/configuration/load_configuration_species_params.c
+++ b/src/cats/configuration/load_configuration_species_params.c
@@ -103,7 +103,8 @@ void load_conf_vital_rate(struct cats_vital_rate *vr, struct cats_configuration
 
         bool req = !vr->preset.have_maximum;
 
-        // printf("%s: %s required %d\n", __func__, vr->name, req);
+        log_message(LOG_INFO, "%s: Loading vital rate '%s' (required: %d)", __func__, vr->name, req);
+
 
         if (vr == &p->carrying_capacity) req = true;
         bool have_max = load_conf_value(req, ini, species_section, vital_rate_maximum, &vr->max_rate);
diff --git a/src/cats/configuration/load_vital_rates.c b/src/cats/configuration/load_vital_rates.c
index 8788a52a48f211031b9c39a48f92f51d0ba8259c..326f244dbc045215eb43f6b798baf7f50c60b330 100644
--- a/src/cats/configuration/load_vital_rates.c
+++ b/src/cats/configuration/load_vital_rates.c
@@ -253,7 +253,9 @@ void conf_load_vital_rate_extended(struct cats_vital_rate *vr, struct cats_confi
         conf_load_vital_rates_density(vr, conf, ini, species_section, param);
 
         char *vital_rate_minimum = compound_string(vr_name, "minimum", sep);
-        load_conf_value(false, ini, species_section, vital_rate_minimum, &vr->min_rate);
+        cats_dt_rates min_rate;
+        load_conf_value(false, ini, species_section, vital_rate_minimum, &min_rate);
+        set_vital_rate_minimum(vr, min_rate);
         free(vital_rate_minimum);
 
         switch (param->parametrization) {
diff --git a/src/cats/configuration/print_configuration.c b/src/cats/configuration/print_configuration.c
index 62655e43a5c964e21406cf4c7e14700d83a7c7b0..8265fe845b4b8d30efb164dc770ade3b11a814a0 100644
--- a/src/cats/configuration/print_configuration.c
+++ b/src/cats/configuration/print_configuration.c
@@ -32,6 +32,7 @@
 #include "preset.h"
 #include "inline_vital_rates.h"
 #include "dispersal/dispersal.h"
+#include "modules/load_module.h"
 
 const char *get_dispersal_name(enum dispersal_type type);
 
@@ -45,7 +46,7 @@ void print_overlay_summary(struct cats_configuration *conf)
 
         for (enum overlay_type ol = OL_NONE + 1; ol < OL_MAX; ol++) {
                 print_category(1, "overlay type", get_overlay_name(ol));
-                print_string(2, "enabled", bool_to_string(conf->overlays.enabled[ol]));
+                print_string(2, "enabled", bool_to_string(conf->overlays.overlay[ol].enabled));
         }
 }
 
@@ -203,7 +204,11 @@ void print_config_summary(struct cats_configuration *conf)
         print_environments(conf, 0);
         log_message(LOG_RAW, "%s\n", TEXT_DIVIDER);
         print_environment_sets(conf);
+
+        log_message(LOG_RAW, "%s\n", TEXT_DIVIDER);
+        print_modules(conf);
         log_message(LOG_RAW, "%s\n", TEXT_DIVIDER);
+
 }
 
 
diff --git a/src/cats/data/cats_datatypes.c b/src/cats/data/cats_datatypes.c
index c0817acfd282833ac0edf5e288bfebbf48259e67..46afd2732690e3820888896b2665732cd6488e33 100644
--- a/src/cats/data/cats_datatypes.c
+++ b/src/cats/data/cats_datatypes.c
@@ -30,3 +30,126 @@
 
 #include "cats_datatypes.h"
 #include "logging.h"
+
+
+const char *true_values[] = {"1", "y", "t"};
+const char *false_values[] = {"0", "n", "f"};
+const int true_counts = (int) (sizeof(true_values) / sizeof(char *));
+const int false_counts = (int) (sizeof(false_values) / sizeof(char *));
+
+
+bool string_to_double(char *string, double *value)
+{
+        if (string == NULL || strlen(string) == 0) return false;
+
+        char *end_pointer = NULL;
+        errno = 0;
+
+        double converted = strtod(string, &end_pointer);
+
+        if (strlen(end_pointer) != 0) {
+                log_message(LOG_WARNING, "%s: invalid or unused characters when converting '%s' to 'double': '%s'",
+                            __func__, string,
+                            end_pointer);
+
+        }
+
+        if (errno == 0) {
+                *value = converted;
+                return true;
+        }
+        return false;
+}
+
+
+bool string_to_float(char *string, float *value)
+{
+        if (string == NULL || strlen(string) == 0) return false;
+
+        char *end_pointer = NULL;
+        errno = 0;
+
+        float converted = strtof(string, &end_pointer);
+
+        if (strlen(end_pointer) != 0) {
+                log_message(LOG_WARNING, "%s: invalid or unused characters when converting '%s' to 'float': '%s'",
+                            __func__, string,
+                            end_pointer);
+                return false;
+        }
+
+        if (errno == 0) {
+                *value = converted;
+                return true;
+        }
+        return false;
+}
+
+
+bool string_to_long_double(char *string, long double *value)
+{
+        if (string == NULL || strlen(string) == 0) return false;
+
+        char *end_pointer = NULL;
+        errno = 0;
+
+        long double converted = strtold(string, &end_pointer);
+
+        if (strlen(end_pointer) != 0) {
+                log_message(LOG_WARNING, "%s: invalid or unused characters when converting '%s' to 'long double': '%s'",
+                            __func__, string, end_pointer);
+                return false;
+        }
+
+        if (errno == 0) {
+                *value = (cats_dt_rates) converted;
+                return true;
+        }
+        return false;
+}
+
+
+bool string_to_bool(char *string, bool *value)
+{
+        if (string == NULL) return false;
+
+        for (int i = 0; i < true_counts; i++) {
+                if (!strncmp(string, true_values[i], 1)) {
+                        *value = true;
+                        return true;
+                }
+        }
+
+        for (int i = 0; i < false_counts; i++) {
+                if (!strncmp(string, false_values[i], 1)) {
+                        *value = false;
+                        return true;
+                }
+        }
+
+        return false;
+}
+
+
+bool string_to_integer(char *string, int32_t *value)
+{
+        if (string == NULL || strlen(string) == 0) return false;
+
+        errno = 0;
+        char *end_pointer = NULL;
+        long converted = strtol(string, &end_pointer, 10);
+
+        if (strlen(end_pointer) != 0) {
+                log_message(LOG_WARNING, "%s: invalid or unused characters when converting '%s' to integer %ld: '%s'",
+                            __func__, string, converted, end_pointer);
+                return false;
+        }
+
+        if (errno == 0) {
+                *value = (int32_t) converted;
+                return true;
+        }
+        return false;
+}
+
+
diff --git a/src/cats/debug/debug_vital_rates.c b/src/cats/debug/debug_vital_rates.c
index 420e03e57ecc613d894d030344842d9bf68859f3..ec6f3625f7bbd5623949155b7a2ecf6e19a1f456 100644
--- a/src/cats/debug/debug_vital_rates.c
+++ b/src/cats/debug/debug_vital_rates.c
@@ -150,7 +150,8 @@ static inline void
 debug_vr_write_line(FILE *f, cats_dt_rates result, cats_dt_environment suit, cats_dt_rates N, cats_dt_rates K_suit,
                     cats_dt_rates K_max, cats_dt_rates density, int32_t dens_idx, const struct cats_grid *grid)
 {
-        fprintf(f, "%0.6Lf,%0.6Lf,%0.6f,%0.6Lf,%0.6Lf,%0.6Lf,%0.6Lf,%0.6Lf,%0.6Lf,%d,%0.6Lf\n",
+        fprintf(f,
+                "%0.6Lf,%0.6Lf,%0.6f,%0.6Lf,%0.6Lf,%0.6Lf,%0.6Lf,%0.6Lf,%0.6Lf,%d,%0.6Lf\n",
                 result,
                 grid->param.scale_factor,
                 suit,
diff --git a/src/cats/debug/debug_vital_rates.h b/src/cats/debug/debug_vital_rates.h
index ee6cddfcfaa2c5171f056db59667dd53dd24b7bc..442e19712f0d92a2fa61655e3ed38fd17f78df87 100644
--- a/src/cats/debug/debug_vital_rates.h
+++ b/src/cats/debug/debug_vital_rates.h
@@ -26,8 +26,6 @@
 #define CATS_DEBUG_VITAL_RATES_H
 #include "configuration/configuration.h"
 
-struct cats_grid *minimal_grid(struct cats_configuration *conf, struct cats_environment *env);
-struct cats_environment *minimal_suitability_environment(void);
 void debug_vital_rates(struct cats_configuration *conf, const struct program_options *command_line_options);
 
 
diff --git a/src/cats/grids/cats_grid.c b/src/cats/grids/cats_grid.c
index 61ba30239c7e478f7371e571a03c7affec6dd205..48daf0cb70b21dfaf6c9d29407ea73eaf5737876 100644
--- a/src/cats/grids/cats_grid.c
+++ b/src/cats/grids/cats_grid.c
@@ -42,10 +42,9 @@
 #include "plants/seeds.h"
 
 #include "stats/grid_stats.h"
-#include "logging.h"
 #include "gdal_load.h"
 #include "grid_setup.h"
-#include "../../memory/cats_memory.h"
+#include "memory/cats_memory.h"
 #include "misc/cats_random.h"
 
 #ifdef USEMPI
@@ -187,7 +186,7 @@ void initialize_grid(struct cats_grid *grid, struct cats_configuration *conf, st
         // FIXME: SCALE FACTOR make this unnecessary and decouple scale factor calculations from **grids
 
         // Attention: we need to call finalize_grid() later if only partially initialised
-        init_statistics(&grid->stats);
+        init_statistics(&grid->stats, &conf->stats_registry);
 
 
         setup_grid_population_structures(grid);
@@ -251,29 +250,25 @@ void cleanup_grid_seeds_and_juveniles(struct cats_grid *grid)
 
         free_grid(&grid->seeds_produced, grid->dimension.rows);
         free_grid(&grid->dispersed_seeds, grid->dimension.rows);
-
         if (grid->seed_bank) {
                 for (cats_dt_coord row = 0; row < grid->dimension.rows; row++) {
                         for (cats_dt_coord col = 0; col < grid->dimension.cols; col++) {
                                 destroy_seed_structure(grid, row, col);
                         }
-
-                        free(grid->seed_bank[row]);
                 }
         }
 
+
         if (grid->juveniles) {
                 for (cats_dt_coord row = 0; row < grid->dimension.rows; row++) {
                         for (cats_dt_coord col = 0; col < grid->dimension.cols; col++) {
                                 destroy_juveniles(grid, row, col);
                         }
 
-                        free(grid->juveniles[row]);
-                        grid->juveniles[row] = NULL;
+                        free(grid->seed_bank[row]);
                 }
         }
 
-
         free(grid->seed_bank);
         free(grid->juveniles);
         grid->juveniles = NULL;
@@ -347,7 +342,7 @@ void do_all_grids(struct cats_grid **grids, struct cats_configuration *conf, gri
 void initialize_grid_stats(struct cats_grid *grid, struct cats_configuration *conf)
 {
         assert(grid != NULL);
-        init_statistics(&grid->stats);
+        init_statistics(&grid->stats, &conf->stats_registry);
         grid->stats.has_been_populated = (char **) new_raw_2d_array_from_dimension(grid->dimension, sizeof(char));
 
         char *filename = get_grid_stat_filename(grid, conf);
@@ -356,10 +351,9 @@ void initialize_grid_stats(struct cats_grid *grid, struct cats_configuration *co
         remove(filename);
         grid->stats.file = fopen(filename, "a+");
         ENSURE_FILE_OPENED(grid->stats.file, filename)
-        if (conf->summary_file) {
-                fprintf(conf->summary_file, "%s,%d,NA,grid-stats,%s\n", conf->run_name, conf->simulation.replicate, filename);
-                fflush(conf->summary_file);
-        }
+
+        if (conf->output.summary_file) fprintf(conf->output.summary_file, "grid-stats,%s,\n",filename);
+
         free(filename);
         write_grid_stats(conf, grid, true);
         fflush(grid->stats.file);
diff --git a/src/cats/grids/gdal_helper.c b/src/cats/grids/gdal_helper.c
index ab8ef1fe46d07abfcaef2904a5bc60c7b917ac50..4a924ab9d2c4b2eae4771f1a7c038418f992aab3 100644
--- a/src/cats/grids/gdal_helper.c
+++ b/src/cats/grids/gdal_helper.c
@@ -166,18 +166,18 @@ void verify_raster_geometry(GDALDatasetH dataset, struct simulation_geometry *ge
 
         char *projection = (char *) GDALGetProjectionRef(dataset);
         if (geometry->projection_string == NULL || strlen(geometry->projection_string) == 0) {
-                log_message(LOG_WARNING, "GEOM: global projection string not set, ignoring projection '%s", projection);
+                log_message(LOG_INFO, "GEOM: global projection string not set, ignoring projection '%s'", projection);
         } else {
                 if (strcmp(projection, geometry->projection_string) != 0) {
                         enum cats_log_level ll;
                         if (geometry->ignore_raster_projection) {
-                                ll = LOG_WARNING;
+                                ll = LOG_INFO;
                         } else {
                                 ll = LOG_ERROR;
                                 errors += 1;
                         }
-                        log_message(ll, "GEOM: global projection string '%s' not equal to projection '%s",
-                                    geometry->projection_string, projection);
+                        // log_message(ll, "GEOM: global projection string '%s' not equal to projection '%s'", geometry->projection_string, projection);
+                        log_message(ll, "GEOM: projection mismatch with reference projection");
 
                 }
 
@@ -234,7 +234,7 @@ create_gdal_dataset(const struct simulation_geometry *geometry, const char *file
         }
 
         GDALSetMetadataItem(dataset, "TIFFTAG_SOFTWARE", cats_version(), NULL);
-        GDALSetMetadataItem(dataset, "TIFFTAG_IMAGEDESCRIPTION", conf->file_content, NULL);
+        GDALSetMetadataItem(dataset, "TIFFTAG_IMAGEDESCRIPTION", conf->output.file_content, NULL);
 
         CSLDestroy(options);
         return dataset;
diff --git a/src/cats/grids/gdal_load.c b/src/cats/grids/gdal_load.c
index d184c729e4f80cdabdfdeb7f4f4b618c9a949125..89efc014e9d04db5c38bc9e478074c12b57028b9 100644
--- a/src/cats/grids/gdal_load.c
+++ b/src/cats/grids/gdal_load.c
@@ -26,7 +26,6 @@
 #include <cpl_string.h>
 #include "data/cats_grid.h"
 #include "configuration/configuration.h"
-#include "logging.h"
 #include "paths/paths.h"
 #include "gdal_load.h"
 #include "overlays/overlays.h"
@@ -141,7 +140,6 @@ void load_gdal_data(const struct raster_load_wrapper *data)
         double nodata = GDALGetRasterNoDataValue(band, &have_nodata);
         if (have_nodata) {
                 log_message(LOG_INFO, "\t'%s': found nodata value %f ", data->filename, nodata);
-
         }
 
         double orig = nodata;
@@ -189,7 +187,7 @@ void load_gdal_data(const struct raster_load_wrapper *data)
                                                 val = NAN;
                                         }
 
-                                        data->target_environment[r][c] = val;
+                                        data->target_environment[r][c] = (cats_dt_environment) val;
 
                                 }
                                 break;
diff --git a/src/cats/grids/gdal_save.c b/src/cats/grids/gdal_save.c
index 74ac287e851a17fad69011723ffe61d2802bea15..b6d25342bc7332ef4447fbbb0aa26d920a439d3b 100644
--- a/src/cats/grids/gdal_save.c
+++ b/src/cats/grids/gdal_save.c
@@ -70,10 +70,9 @@ void *save_population_to_gdal(struct cats_grid *grid, struct cats_configuration
                         (cats_dt_population) get_vital_rate_maximum(&grid->param.carrying_capacity));
                 fflush(stderr);
         }
-        if (conf->summary_file){
-                fprintf(conf->summary_file, "%s,%d,%d,population,%s\n",conf->run_name, conf->simulation.replicate, conf->time.year_current, filename);
-                fflush(conf->summary_file);
-        }
+
+        if (conf->output.summary_file) fprintf(conf->output.summary_file, "population,%s,%d\n",filename, conf->time.year_current);
+
 
         free(filename);
         return 0;
@@ -279,7 +278,7 @@ save_grid_to_gdal(struct grid_wrapper *gridwrapper, GDALDataType out_datatype, c
         set_gdal_dataset_geo_info(dataset, &conf->geometry, &gridwrapper->offset);
         set_gdal_band_nodata(band, nodata_value);
         set_gdal_band_metadata(band, GCI_GrayIndex, description);
-        set_gdal_dataset_metadata(dataset, "configuration", conf->file_content);
+        set_gdal_dataset_metadata(dataset, "configuration", conf->output.file_content);
 
         band = GDALGetRasterBand(dataset, 1);
 
diff --git a/src/cats/hybrid/scalefactor.c b/src/cats/hybrid/scalefactor.c
index 63ded6208db510824ba8c799a9712c53cd07280a..003a61bd0b8d6e7b101e58184a188cab6ba7d41f 100644
--- a/src/cats/hybrid/scalefactor.c
+++ b/src/cats/hybrid/scalefactor.c
@@ -42,34 +42,47 @@
 #include "lambda/leslie_matrix.h"
 #include "lambda/lambda.h"
 
+struct cats_module *get_demographic_module_for_matrix(struct cats_configuration *conf, int32_t species_id)
+{
+        struct cats_module *module = NULL;
+        struct cats_species_param *param = &conf->param[species_id];
+        if (param->demographic_module == NULL) return module;
+
+        int32_t module_id = param->demographic_module->id;
+
+        if (conf->modules.module[module_id].flags & MODULE_ALTERNATE_DEMOGRAPHIC) return &conf->modules.module[module_id];
+
+        return NULL;
+}
+
+
+bool have_leslie_matrix_function_for_module(struct cats_module *module)
+{
+        if (module == NULL) return true;
+        cats_leslie_matrix_func func = module->create_leslie_matrix;
+        return func != NULL;
+}
 
 cats_dt_rates calculate_scale_factor_from_lambda(struct cats_configuration *conf, int32_t species_id)
 {
         assert(species_id < conf->param_count);
         struct cats_species_param *param = &conf->param[species_id];
-        bool ok = false;
-        cats_leslie_matrix_func func = NULL;
-
-
-        // FIXME LOOKS LIKE A MESS
-        if (param->custom_vital_rates == false || param->custom_vital_ages == false) ok = true;
-        if (param->custom_vital_rates || param->custom_vital_ages) {
-                if (param->demographic_module != NULL) {
-                        int32_t id = param->demographic_module->id;
-                        if (conf->modules.module[id].flags & MODULE_ALTERNATE_DEMOGRAPHIC) {
-                                func = conf->modules.module[id].create_leslie_matrix;
-                                if (func != NULL) {
-                                        ok = true;
-                                }
-                        }
-                }
-        }
 
-        if (!ok) {
+
+        struct cats_module *module = get_demographic_module_for_matrix(conf, species_id);
+        bool have_func = have_leslie_matrix_function_for_module(module);
+        log_message(LOG_IMPORTANT, "Demographic module %p", module);
+
+        if (module != NULL && have_func == false) {
+
                 log_message(LOG_WARNING, "unable to calculate scale factor for %s: custom vital rates or vital ages",
                             param->species_name);
                 log_message(LOG_WARNING, "Using scale factor 0.5 for %s", param->species_name);
                 return 0.5;
+
+        } else if (module != NULL) {
+                log_message(LOG_IMPORTANT, "Grid %d uses module %s for demography: %p", species_id,
+                module->canonical_name, module->create_leslie_matrix);
         }
 
 
@@ -104,9 +117,11 @@ cats_dt_rates calculate_scale_factor_from_lambda(struct cats_configuration *conf
                 conf->param[species_id].scale_factor = s;
                 conf->direct_scale_factor = s;
                 cats_dt_rates old_s = s;
-                lambda = calculate_lambda(conf, &l_param, true, func);
-                log_message(LOG_INFO, "iteration %d: scale factor %0.25f resulted in lambda %0.25f", iterations,
-                            (double) s, (double) lambda);
+
+                lambda = calculate_lambda(conf, &l_param, true, module);
+                log_message(LOG_INFO, "iteration %d: scale factor %0.25Lf resulted in lambda %0.25Lf", iterations, s,
+                            lambda);
+
 
                 if (fabsl(lambda - 1.0) < threshold) {
                         conf->lambda_at_OT = lambda;
@@ -168,7 +183,7 @@ cats_dt_rates calculate_scale_factor_from_lambda(struct cats_configuration *conf
                     iterations);
         conf->param[species_id].scale_factor = s;
         conf->direct_scale_factor = s;
-        calculate_lambda(conf, &l_param, false, func);
+        calculate_lambda(conf, &l_param, false, module);
 
 
         return s;
@@ -177,7 +192,9 @@ cats_dt_rates calculate_scale_factor_from_lambda(struct cats_configuration *conf
 
 void set_scale_factor_all_species(struct cats_configuration *conf, cats_dt_rates scale_factor)
 {
+
         log_message(LOG_INFO, "setting scale factor for all grids directly to %f", (double) scale_factor);
+
         for (int id = 0; id < conf->grid_count; id++) {
                 conf->param[id].scale_factor = scale_factor;
         }
@@ -207,7 +224,10 @@ void setup_scale_factor(struct cats_configuration *conf, struct program_options
 
         if (options->direct_scale_factor > 0 && options->direct_scale_factor < 1) {
                 conf->direct_scale_factor = options->direct_scale_factor;
+
                 log_message(LOG_IMPORTANT, "using pre-calculated scale factor %f", (double) conf->direct_scale_factor);
+
+
                 set_scale_factor_all_species(conf, conf->direct_scale_factor);
                 return;
         }
diff --git a/src/cats/inline_overlays.h b/src/cats/inline_overlays.h
index eb519de82e3ac2def8e549fd9bcc679c79b076c2..519689e39cf39c53b519c1de0e5529f6948fc49e 100644
--- a/src/cats/inline_overlays.h
+++ b/src/cats/inline_overlays.h
@@ -34,7 +34,7 @@ static inline bool
 cell_excluded_by_habitat(const struct cats_configuration *config, cats_dt_coord row, cats_dt_coord col)
 {
         if (!config->overlays.have_overlays) return false;
-        if (!config->overlays.enabled[OL_HABITAT_TYPE_CC]) return false;
+        if (!config->overlays.overlay[OL_HABITAT_TYPE_CC].enabled) return false;
 
         return (config->overlays.habitat_cc->data[row][col] == 0.0);
 }
@@ -45,7 +45,7 @@ cell_excluded_by_overlay(const struct cats_configuration *config, cats_dt_coord
 {
         assert(config != NULL);
 
-        if (config->overlays.have_overlays == false || config->overlays.enabled[OL_EXCLUSION] == false) return false;
+        if (config->overlays.have_overlays == false || config->overlays.overlay[OL_EXCLUSION].enabled == false) return false;
 
         assert(config->overlays.exclusion->data != NULL);
 
diff --git a/src/cats/lambda/lambda-grid.c b/src/cats/lambda/lambda-grid.c
index 4573989fc3e8725345a1fc8960419c4e2bf8cb52..501cc0f6298d62c54685e63c1265fda18c7de0f1 100644
--- a/src/cats/lambda/lambda-grid.c
+++ b/src/cats/lambda/lambda-grid.c
@@ -78,13 +78,15 @@ lookup_lambda(struct cats_configuration *conf, struct cats_grid *grid, cats_dt_p
                         l_param.suitability = NAN;
                         break;
         }
+        /*
         cats_leslie_matrix_func func = NULL;
         if (grid->param.demographic_module && grid->param.demographic_module->create_leslie_matrix) {
                 func = grid->param.demographic_module->create_leslie_matrix;
         }
+         */
         // we have density dependence
         if (N >= 0 || grid->param.parametrization != PARAM_HYBRID) {
-                return calculate_lambda(conf, &l_param, true, func);
+                return calculate_lambda(conf, &l_param, true, NULL);
 
         }
         assert(grid->param.parametrization == PARAM_HYBRID);
@@ -100,7 +102,7 @@ lookup_lambda(struct cats_configuration *conf, struct cats_grid *grid, cats_dt_p
                 return param->lambda_cache[index];
         } else {
                 l_param.N = 0;
-                cats_dt_rates lambda = calculate_lambda(conf, &l_param, true, func);
+                cats_dt_rates lambda = calculate_lambda(conf, &l_param, true, NULL);
                 param->lambda_cache[index] = lambda;
                 return lambda;
         }
diff --git a/src/cats/lambda/lambda.c b/src/cats/lambda/lambda.c
index 4df3836972545bda35aa98736f08fe1198b0ce5f..cbd08c651b552de9fba6dea1b87fbe379c1ffb82 100644
--- a/src/cats/lambda/lambda.c
+++ b/src/cats/lambda/lambda.c
@@ -39,18 +39,29 @@ void debug_lambda_problem(struct cats_configuration *conf, gsl_complex largest_e
 
 
 cats_dt_rates calculate_lambda(struct cats_configuration *conf, struct lambda_parameters *l_param, bool silent,
-                               cats_leslie_matrix_func leslie_matrix_func)
+                               struct cats_module *module)
 {
-        int32_t N = 0;
-        bool debug = conf->command_line_options.debug_flags & DEBUG_LAMBDA;
-        const int32_t species_id = l_param->species_id;
+
 
         double *matrix = NULL;
-        if (leslie_matrix_func == NULL) {
+        int32_t N = 0;
+
+        if (module == NULL) {
                 matrix = create_leslie_matrix(conf, l_param, silent, &N);
+        } else if (module->create_leslie_matrix != NULL)  {
+                matrix = module->create_leslie_matrix(conf, l_param, silent, &N);
+
         } else {
-                matrix = leslie_matrix_func(conf, l_param, silent, &N);
+                log_message(LOG_ERROR, "no function to create leslie matrix specified");
+                exit_cats(EXIT_FAILURE);
         }
+
+
+        bool debug = conf->command_line_options.debug_flags & DEBUG_LAMBDA;
+        const int32_t species_id = l_param->species_id;
+
+
+
         //
 
         double *matrix_copy = NULL;
@@ -76,8 +87,12 @@ cats_dt_rates calculate_lambda(struct cats_configuration *conf, struct lambda_pa
                 print_matrix(matrix_copy, N, N);
                 log_message(LOG_RAW, "Schur form T:\n");
                 print_matrix(matrix, N, N);
-                debug_lambda_1_found(conf, species_id, largest_eigen_value, matrix_copy, N, l_param);
-                log_message(LOG_RAW, "============================\n");
+
+                if (module == NULL) {
+                        debug_lambda_1_found(conf, species_id, largest_eigen_value, matrix_copy, N, l_param);
+                        log_message(LOG_RAW, "============================\n");
+                }
+
         }
 
         if (silent == false && l_param->calculate_scale == true) {
@@ -85,7 +100,9 @@ cats_dt_rates calculate_lambda(struct cats_configuration *conf, struct lambda_pa
                 if (fabs(GSL_REAL(largest_eigen_value) - 1.0) < LAMBDA_EPS) {
                         log_message(LOG_RAW, "Final matrix\n");
                         print_matrix(matrix_copy, N, N);
-                        debug_lambda_1_found(conf, species_id, largest_eigen_value, matrix_copy, N, l_param);
+                        if (module == NULL) {
+                                debug_lambda_1_found(conf, species_id, largest_eigen_value, matrix_copy, N, l_param);
+                        }
                 }
         }
 
diff --git a/src/cats/lambda/lambda.h b/src/cats/lambda/lambda.h
index a2e0f2dbcef671a2f82443097bdf865f7edcf013..96ec98d68a0b23d34b2f4289f5aac94c960c4248 100644
--- a/src/cats/lambda/lambda.h
+++ b/src/cats/lambda/lambda.h
@@ -28,6 +28,6 @@
 #include "leslie_matrix.h"
 
 cats_dt_rates calculate_lambda(struct cats_configuration *conf, struct lambda_parameters *l_param, bool silent,
-                               cats_leslie_matrix_func leslie_matrix_func);
+                               struct cats_module *module);
 
 #endif //CATS_LAMBDA_H
diff --git a/src/cats/lambda/leslie_matrix.c b/src/cats/lambda/leslie_matrix.c
index 413a266d4fe75e46dff700823f5c9f8a259b0735..7562e3d1304334ce934f62116d30a0eda1ea88ca 100644
--- a/src/cats/lambda/leslie_matrix.c
+++ b/src/cats/lambda/leslie_matrix.c
@@ -36,6 +36,43 @@
 #include "inline_vital_rates.h"
 
 
+cats_dt_rates
+calculate_rate_for_matrix(struct cats_vital_rate *rate,
+                    const struct lambda_parameters *l_param,
+                    bool print)
+{
+        assert(l_param != NULL);
+        assert(l_param->param != NULL);
+
+        const struct cats_species_param *param = l_param->param; //&conf->param[grid_id];
+
+        const cats_dt_rates N = l_param->N;
+        const cats_dt_rates K = l_param->K;
+        struct link_override_parameters override = {-1};
+
+        if (param->parametrization == PARAM_HYBRID) {
+                override.override_suitability = l_param->suitability;
+        }
+
+        override.override_population = N;
+        override.override_carrying_capacity = l_param->K;
+
+        cats_dt_rates value = calculate_rate(rate, N, param, l_param->grid, l_param->row, l_param->col, &override);
+
+        if (print) {
+                if (param->parametrization == PARAM_HYBRID) {
+                        log_message(LOG_INFO, "%s: %Lf (maximum %Lf) for population %Lf/%Lf at suitability %Lf",
+                                    rate->name, value, rate->max_rate, N, K, l_param->suitability);
+                } else {
+                        log_message(LOG_INFO, "%s: %Lf (maximum %Lf) for population %Lf/%Lf",
+                                    rate->name, value, rate->max_rate, N, K);
+                }
+
+        }
+
+        return value;
+}
+
 cats_dt_rates
 get_rate_for_matrix(enum cats_vital_rate_id rate_type,
                     const struct lambda_parameters *l_param,
diff --git a/src/cats/lambda/leslie_matrix.h b/src/cats/lambda/leslie_matrix.h
index 25604c6c9887790293b1212d8b639865a289adca..66cb8590d7b262653ded9737e3df8019585dde36 100644
--- a/src/cats/lambda/leslie_matrix.h
+++ b/src/cats/lambda/leslie_matrix.h
@@ -43,6 +43,10 @@ cats_dt_rates
 get_rate_for_matrix(enum cats_vital_rate_id rate_type, const struct lambda_parameters *l_param,
                     bool print);
 
+cats_dt_rates
+calculate_rate_for_matrix(struct cats_vital_rate *rate,
+                          const struct lambda_parameters *l_param,
+                          bool print);
 double *
 create_leslie_matrix(struct cats_configuration *conf, struct lambda_parameters *l_param, bool silent, int32_t *N_out);
 
diff --git a/src/cats/misc/cats_random.c b/src/cats/misc/cats_random.c
index 878dbe7a33334c5d6513943e927fa65f5071a63e..238e437a7bd55186f3e424843e6029545753998a 100644
--- a/src/cats/misc/cats_random.c
+++ b/src/cats/misc/cats_random.c
@@ -64,6 +64,39 @@ unsigned int get_random_seed(bool show)
 }
 
 
+uint64_t get_random_seed64(bool show)
+{
+        uint64_t seed;
+        struct timeval tv;
+#ifdef CATS_ON_WINDOWS
+        get_time(&tv);
+        seed = tv.tv_sec + tv.tv_usec;
+#else
+        const char *device = "/dev/urandom";
+        FILE *random = fopen(device, "r");
+
+        if (random == NULL) {
+                get_time(&tv);
+                seed = tv.tv_sec + tv.tv_usec;
+        } else {
+                size_t read = fread(&seed, sizeof(seed), 1, random);
+                if (read <= 0) {
+                        log_message(LOG_ERROR, "could not read from %s, using time instead", device);
+                        get_time(&tv);
+                        seed = tv.tv_sec + tv.tv_usec;
+                }
+
+                fclose(random);
+
+                if (show) log_message(LOG_IMPORTANT, "using seed from %s: %"PRId64, device, seed);
+        }
+#endif
+        if (show) log_message(LOG_INFO, "random seed value: %"PRId64, seed);
+
+        return seed;
+}
+
+
 gsl_rng *allocate_rng(void)
 {
         gsl_rng *r = gsl_rng_alloc(gsl_rng_mt19937);
@@ -103,6 +136,7 @@ int32_t poisson_undampened(const gsl_rng *rng, cats_dt_rates value)
         if (result > CATS_MAX_POPULATION) {
                 log_message(LOG_WARNING, "poisson called on %f resulted in %f > %d, returning %d",
                             (double) value, (double) result, CATS_MAX_POPULATION, CATS_MAX_POPULATION);
+
                 return CATS_MAX_POPULATION;
         }
         int32_t final = (int32_t) result;
diff --git a/src/cats/misc/cats_random.h b/src/cats/misc/cats_random.h
index 2c9da9103ff5a82375505323eecf280b3e88f893..7a2b7827247fa7cd2f897474db13355ba337e0e3 100644
--- a/src/cats/misc/cats_random.h
+++ b/src/cats/misc/cats_random.h
@@ -43,4 +43,6 @@ cats_dt_seeds poisson_seeds_undampened(const gsl_rng *rng, cats_dt_rates value);
 
 cats_dt_seeds poisson_seeds_capped(const gsl_rng *rng, cats_dt_rates value, cats_dt_seeds cap);
 
+
+uint64_t get_random_seed64(bool show);
 #endif //CATS_CATS_RANDOM_H
diff --git a/src/cats/modules/load_module.c b/src/cats/modules/load_module.c
index ad1f8c20dd602e18cb9dead2bf6f0e59a9994e77..4089dbf6a9c6ec1fd5a6af5fd279e3d6fb56ad55 100644
--- a/src/cats/modules/load_module.c
+++ b/src/cats/modules/load_module.c
@@ -107,7 +107,7 @@ int32_t register_module(struct cats_configuration *conf, const char *module_name
         int32_t module_id = conf->modules.count;
         CATS_MODULE_ID_INTERNAL = module_id;
         CATS_MODULE_DATA_INTERNAL = module_data;
-        logging_initialize(conf->command_line_options.default_log_level, &conf->global->time_info, conf->log_file_name, conf->quiet);
+        logging_initialize(conf->command_line_options.default_log_level, &conf->global->time_info, conf->output.log_file_name, conf->quiet);
         logging_set_module_name(module_name);
 
         conf->modules.module[module_id].flags = module_flags;
@@ -148,8 +148,8 @@ void register_load_species_param_config_func(struct cats_configuration *conf, ca
 
 void register_create_leslie_matrix_func(struct cats_configuration *conf, cats_leslie_matrix_func func)
 {
-        conf->param[0].module_data[CATS_MODULE_ID].create_leslie_matrix_function = func;
         conf->modules.module[CATS_MODULE_ID].create_leslie_matrix = func;
+        log_message(LOG_INFO, "Using leslie matrix function at address %p", conf->modules.module[CATS_MODULE_ID].create_leslie_matrix);
 }
 
 
@@ -167,6 +167,12 @@ void register_module_vital_rate(struct cats_configuration *conf, struct cats_vit
         vr->is_carrying_capacity = false;
 
         strncpy(vr->name, name, MAX_VITAL_NAME_LEN + 1);
+        vr->default_rate_id = m->vital_rate_count;
 
         m->vital_rate_count++;
+}
+
+void add_module_output_directory(struct cats_configuration *conf, const char *name)
+{
+        string_array_add(conf->output.needed_module_output_directories, name);
 }
\ No newline at end of file
diff --git a/src/cats/modules/load_module.h b/src/cats/modules/load_module.h
index 38dd81e811b7c6f8ff546c4e89c1e70beda751d6..bd87c6049a1eda9d523de4d79598388e476f55c2 100644
--- a/src/cats/modules/load_module.h
+++ b/src/cats/modules/load_module.h
@@ -50,4 +50,6 @@ void register_load_species_param_config_func(struct cats_configuration *conf, ca
 
 void print_modules(struct cats_configuration *conf);
 
+void add_module_output_directory(struct cats_configuration *conf, const char *name);
+
 #endif //CATS_LOAD_MODULE_H
diff --git a/src/cats/modules/modules.c b/src/cats/modules/modules.c
index 68dc7f18cf7dbd64d42f19be4a0f416fa3e9cd55..a1949e5c9dfb37eae8bbabc03d76f2b0a4c72300 100644
--- a/src/cats/modules/modules.c
+++ b/src/cats/modules/modules.c
@@ -28,7 +28,6 @@
 #include "modules.h"
 #include "cats_strings/cats_strings.h"
 
-
 void init_cats_module(struct cats_module *module, int32_t id)
 {
         module->name = NULL;
@@ -38,6 +37,8 @@ void init_cats_module(struct cats_module *module, int32_t id)
         module->id = id;
         module->create_leslie_matrix = NULL;
         module->stats_header = NULL;
+
+
 }
 
 
@@ -59,7 +60,6 @@ void init_module_species_data(struct module_species_data *m)
         m->grid_init_function = NULL;
         m->grid_cleanup_function = NULL;
         m->load_species_param_function = NULL;
-        m->create_leslie_matrix_function = NULL;
         m->grid_stat_function = NULL;
         m->module = NULL;
         m->vital_rate_count = 0;
diff --git a/src/cats/modules/modules.h b/src/cats/modules/modules.h
index d24d85b36bc14f8282674d73bc8f3a40ddf53b7b..cdae0e9b520950adb93a952318f84d37a1cc632a 100644
--- a/src/cats/modules/modules.h
+++ b/src/cats/modules/modules.h
@@ -23,12 +23,11 @@
 
 #ifndef CATS_MODULES_H
 #define CATS_MODULES_H
-
 #include <stdint.h>
 #include <stdbool.h>
 #include "defaults.h"
 #include "data/cats_datatypes.h"
-
+#include <gsl/gsl_eigen.h>
 struct cats_ini;
 struct cats_configuration;
 struct lambda_parameters;
@@ -53,13 +52,14 @@ typedef struct string_array *(*cats_grid_stat_function)(struct cats_configuratio
 typedef void (*cats_cell_function)(struct cats_configuration *conf, struct cats_grid *grid, cats_dt_coord row,
                                    cats_dt_coord col, cats_dt_population K);
 
+typedef void (*eigen_system_print_func)(gsl_complex eigen_value, gsl_vector_complex_view *eigen_vector, int32_t N, struct cats_species_param *param);
+
 struct module_species_data {
         struct cats_module *module;
         cats_grid_function grid_init_function;
         cats_grid_function grid_cleanup_function;
 
         cats_load_species_param_function load_species_param_function;
-        cats_leslie_matrix_func create_leslie_matrix_function;
         cats_grid_stat_function grid_stat_function;
         cats_cell_function cell_destroyed_action;
         cats_cell_function cell_carrying_capacity_action;
@@ -68,7 +68,6 @@ struct module_species_data {
         char **vital_rate_name;
         bool *vital_rate_required;
         int32_t vital_rate_count;
-        void *module_data;
 };
 
 
@@ -79,13 +78,16 @@ enum cats_module_flags {
 
 };
 
+
+
 struct cats_module {
         char *name;
         char *canonical_name;
         char *filename;
         void *data;
         enum cats_module_flags flags;
-        void *create_leslie_matrix;
+        cats_leslie_matrix_func create_leslie_matrix;
+
         int32_t id;
         struct string_array *stats_header;
 };
diff --git a/src/cats/overlays/overlay_exclusion.c b/src/cats/overlays/overlay_exclusion.c
index 559b40ee97be7b3e21cd46e07574421e304b6007..f68b8691b9e9215d4b0b9552f13962cffe458b2b 100644
--- a/src/cats/overlays/overlay_exclusion.c
+++ b/src/cats/overlays/overlay_exclusion.c
@@ -39,8 +39,8 @@
 int64_t get_non_excluded_cell_count(struct cats_configuration *conf, struct cats_grid *grid)
 {
         int64_t all_count = raster_cell_count(grid->dimension);
-        if (conf->overlays.have_overlays && conf->overlays.enabled[OL_EXCLUSION]) {
-                all_count -= conf->overlays.excluded_cells;
+        if (conf->overlays.have_overlays && conf->overlays.overlay[OL_EXCLUSION].enabled) {
+                all_count -= conf->overlays.overlay[OL_EXCLUSION].excluded_cells;
         }
 
         return all_count;
@@ -58,24 +58,30 @@ void destroy_excluded_cells(const struct cats_configuration *conf, struct cats_g
                 for (cats_dt_coord col = 0; col < cols; col++) {
                         if (cell_excluded_by_overlay(conf, row, col) == false) continue;
 
-                        destroyed_cell_count += 1;
+                        cats_dt_population old_population = get_adult_population(grid, row, col);
 
                         if (grid->param.default_demographics) {
+
                                 set_population_ignore_cc(grid, row, col, 0); // destroy_excluded_cells
                                 destroy_plant_cell(grid, row, col);
+                                if (old_population) destroyed_cell_count += 1;
                         } else {
+                                bool destroyed = false;
                                 for (int32_t i = 0; i < conf->modules.count; i++) {
                                         cats_cell_function destroy = grid->param.module_data[i].cell_destroyed_action;
                                         if (destroy != NULL) {
                                                 destroy(grid->conf, grid, row, col, 0);
+                                                destroyed = true;
                                         }
                                 }
+                                if (old_population && destroyed) destroyed_cell_count += 1;
                         }
 
                 }
         }
 
-        log_message(LOG_INFO, "grid %d (%s): %"PRIi64" cells pruned", grid->id, grid->param.species_name,
+
+        log_message(LOG_INFO, "Removed excluded cell for grid %d (%s): %ld cells removed", grid->id, grid->param.species_name,
                     destroyed_cell_count);
 }
 
@@ -84,8 +90,6 @@ void destroy_excluded_cells_all_grids(const struct cats_configuration *conf, str
 {
         if (grid->id != 0) return;
 
-        log_message(LOG_INFO, "Purging all excluded cells");
-
         const int32_t grid_count = conf->grid_count;
         struct cats_grid **parent = grid->parent;
 
diff --git a/src/cats/overlays/overlay_habitat_type_cc.c b/src/cats/overlays/overlay_habitat_type_cc.c
index a349ef002bad33555acbf02a026d516ae165d72b..e98c258f98d2059292ecac0f7667e78c9d4697c2 100644
--- a/src/cats/overlays/overlay_habitat_type_cc.c
+++ b/src/cats/overlays/overlay_habitat_type_cc.c
@@ -107,8 +107,9 @@ double get_habitat_cc_multiplier(const struct habitat_layer_cc_aux *aux, int32_t
                             aux->max_code);
                 exit(EXIT_FAILURE);
         }
-
-        return aux->habitat_multipliers[habitat_type];
+        double result = aux->habitat_multipliers[habitat_type];
+        assert(result >= 0.0);
+        return result;
 }
 
 
diff --git a/src/cats/overlays/overlay_resources.c b/src/cats/overlays/overlay_resources.c
new file mode 100644
index 0000000000000000000000000000000000000000..64a171f856e369a527a9a5d3a804722574f8f4d9
--- /dev/null
+++ b/src/cats/overlays/overlay_resources.c
@@ -0,0 +1,30 @@
+#include <assert.h>
+#include <math.h>
+#include "overlay_resources.h"
+#include "memory/arrays.h"
+
+
+struct cats_2d_array_double *translate_resources(const struct cats_2d_array_double *data)
+{
+        struct cats_2d_array_double *result = new_2d_array_double(data->dimension);
+
+        const cats_dt_coord rows = data->dimension.rows;
+        const cats_dt_coord cols = data->dimension.cols;
+
+        for (cats_dt_coord row = 0; row < rows; row++) {
+                for (cats_dt_coord col = 0; col < cols; col++) {
+
+                        double value = data->data[row][col];
+
+                        if (isnan(value)) {
+                                result->data[row][col] = 0.0;
+                        } else {
+
+                                if (value <0 ) value = 0;
+                                result->data[row][col] = value;
+                        }
+                }
+        }
+
+        return result;
+}
diff --git a/src/cats/overlays/overlay_resources.h b/src/cats/overlays/overlay_resources.h
new file mode 100644
index 0000000000000000000000000000000000000000..012b5acdd11a3310cad339e5a391010d71be8a93
--- /dev/null
+++ b/src/cats/overlays/overlay_resources.h
@@ -0,0 +1,8 @@
+//
+// Created by andreas on 08/02/23.
+//
+
+#ifndef CATS_OVERLAY_RESOURCES_H
+#define CATS_OVERLAY_RESOURCES_H
+struct cats_2d_array_double *translate_resources(const struct cats_2d_array_double *data);
+#endif //CATS_OVERLAY_RESOURCES_H
diff --git a/src/cats/overlays/overlays.c b/src/cats/overlays/overlays.c
index c20c097519449721d90e2716eabe0de5bbc77fef..3d7c17b7e334992a800d3687f23df0d7d7518fe7 100644
--- a/src/cats/overlays/overlays.c
+++ b/src/cats/overlays/overlays.c
@@ -33,7 +33,8 @@
 #include "grids/cats_grid.h"
 #include "overlay_habitat_type_cc.h"
 #include "overlay_exclusion.h"
-
+#include "overlay_resources.h"
+#include "paths/path_patterns.h"
 
 const char *get_overlay_name(enum overlay_type type)
 {
@@ -42,10 +43,12 @@ const char *get_overlay_name(enum overlay_type type)
                         return "exclusion";
                 case OL_HABITAT_TYPE_CC:
                         return "habitat_type_cc";
-
+                case OL_RESOURCE:
+                        return "resources";
+                        break;
                 case OL_NONE:
                 case OL_MAX:
-                default:
+                case OL_CUSTOM:
                         break;
         }
 
@@ -57,30 +60,47 @@ void cleanup_overlays(struct cats_overlays *overlays)
 {
         if (overlays->exclusion) free_cats_grid(&overlays->exclusion);
         if (overlays->habitat_cc) free_cats_grid(&overlays->habitat_cc);
-        if (overlays->aux_data[OL_HABITAT_TYPE_CC]) {
-                cleanup_habitat_layer_cc_aux(&overlays->aux_data[OL_HABITAT_TYPE_CC]);
+        if (overlays->overlay[OL_HABITAT_TYPE_CC].aux_data) {
+                cleanup_habitat_layer_cc_aux(&overlays->overlay[OL_HABITAT_TYPE_CC].aux_data);
         }
 
         for (int32_t i = 0; i < OL_MAX; i++) {
-                free(overlays->filename[i]);
-                overlays->filename[i] = NULL;
-                free(overlays->filename_aux[i]);
-                overlays->filename_aux[i] = NULL;
-                free(overlays->filename_loaded[i]);
-                overlays->filename_loaded[i] = NULL;
-                free(overlays->name[i]);
-                overlays->name[i] = NULL;
+                free(overlays->overlay[i].filename);
+                overlays->overlay[i].filename = NULL;
+                free(overlays->overlay[i].filename_aux);
+                overlays->overlay[i].filename_aux = NULL;
+                free(overlays->overlay[i].filename_loaded);
+                overlays->overlay[i].filename_loaded = NULL;
+                free(overlays->overlay[i].name);
+                overlays->overlay[i].name = NULL;
         }
 
 }
 
+struct cats_overlay *add_overlay(struct cats_configuration *conf, const char *name)
+{
+        struct cats_overlay *overlay = NULL;
+        abort_implemented(__func__);
+        return overlay;
+
+}
+
 
-enum overlay_type get_overlay_type_from_name(const char *name)
+enum overlay_type get_overlay_type_from_name(const char *name, const struct string_array *registered_names)
 {
         if (!name) return OL_NONE;
 
         if (!strcmp(name, "exclusion")) return OL_EXCLUSION;
         if (!strcmp(name, "habitat carrying capacity")) return OL_HABITAT_TYPE_CC;
+        if (!strcmp(name, "resources")) return OL_RESOURCE;
+
+        if (registered_names != NULL) {
+                for (int32_t i = 0; i < registered_names->count; i++) {
+                        if (!strcmp(name, registered_names->string[i])) {
+                                return OL_CUSTOM;
+                        }
+                }
+        }
 
         log_message(LOG_ERROR, "%s: unknown overlay type '%s'", __func__, name);
         exit(EXIT_FAILURE);
@@ -94,17 +114,19 @@ void reset_overlay(struct cats_overlays *overlay)
         overlay->exclusion = NULL;
         overlay->cols = 0;
         overlay->rows = 0;
-        overlay->excluded_cells = 0;
+
 
         for (int32_t i = 0; i < OL_MAX; i++) {
-                overlay->enabled[i] = false;
-                overlay->is_static[i] = true;
-                overlay->reload_interval[i] = 0;
-                overlay->filename[i] = NULL;
-                overlay->filename_aux[i] = NULL;
-                overlay->filename_loaded[i] = NULL;
-                overlay->aux_data[i] = NULL;
-                overlay->name[i] = NULL;
+                overlay->overlay[i].enabled = false;
+                overlay->overlay[i].excluded_cells = 0;
+
+                overlay->overlay[i].is_static = true;
+                overlay->overlay[i].reload_interval = 0;
+                overlay->overlay[i].filename = NULL;
+                overlay->overlay[i].filename_aux = NULL;
+                overlay->overlay[i].filename_loaded = NULL;
+                overlay->overlay[i].aux_data = NULL;
+                overlay->overlay[i].name = NULL;
         }
 
 }
@@ -117,27 +139,37 @@ void load_overlay_from_file(struct cats_configuration *conf, enum overlay_type t
                 log_message(LOG_ERROR, "overlay type %d out of range", type);
         }
         log_message(LOG_INFO, "gdal: loading grid mask %s", filename);
-        struct cats_2d_array_double *raw_values = get_double_values_from_gdal(&conf->geometry, filename, false, false);
+        char *filename_substituted = filename_pattern_substitution(filename, conf, conf->time.year_current);
+
+        struct cats_2d_array_double *raw_values = get_double_values_from_gdal(&conf->geometry, filename_substituted, false, false);
 
         check_raster_dimensions(raw_values->dimension, conf->geometry.dimension);
 
-        if (conf->overlays.filename_loaded[type]) free(conf->overlays.filename_loaded[type]);
-        conf->overlays.filename_loaded[type] = strdup(filename);
+        if (conf->overlays.overlay[type].filename_loaded) free(conf->overlays.overlay[type].filename_loaded);
+        conf->overlays.overlay[type].filename_loaded = strdup(filename);
 
         switch (type) {
                 case OL_EXCLUSION:
                         if (conf->overlays.exclusion) free_cats_grid(&conf->overlays.exclusion);
-                        conf->overlays.exclusion = translate_exclusion(raw_values, &conf->overlays.excluded_cells);
+                        conf->overlays.exclusion = translate_exclusion(raw_values, &conf->overlays.overlay[type].excluded_cells);
                         break;
                 case OL_HABITAT_TYPE_CC:
                         if (conf->overlays.habitat_cc) free_cats_grid(&conf->overlays.habitat_cc);
-                        conf->overlays.habitat_cc = translate_habitat(raw_values, conf->overlays.aux_data[type]);
+                        conf->overlays.habitat_cc = translate_habitat(raw_values, conf->overlays.overlay[type].aux_data);
+                        break;
+                case OL_CUSTOM:
+                        log_message(LOG_ERROR, "OL_CUSTOM not implemented yet");
+                        exit(EXIT_FAILURE);
+                case OL_RESOURCE:
+                        if (conf->overlays.resources) free_cats_grid(&conf->overlays.resources);
+                        conf->overlays.resources = translate_resources(raw_values);
                         break;
 
                 case OL_NONE:
                 case OL_MAX:
                         break;
-        }
 
+        }
+        free(filename_substituted);
         free_cats_grid(&raw_values);
 }
diff --git a/src/cats/overlays/overlays.h b/src/cats/overlays/overlays.h
index 9b504e6f451c29de08d7a4be08444ecd74260bf0..cc1f4a8fffa366c36dde9395bb9a2cbf94da9ed9 100644
--- a/src/cats/overlays/overlays.h
+++ b/src/cats/overlays/overlays.h
@@ -36,6 +36,8 @@ enum overlay_type {
         OL_NONE = 0,
         OL_EXCLUSION = 1,
         OL_HABITAT_TYPE_CC = 2,
+        OL_RESOURCE = 3,
+        OL_CUSTOM = 4,
         OL_MAX
 };
 
@@ -46,6 +48,24 @@ enum ol_exclusion_type {
         OL_EXCLUSION_NAN = 2,
 };
 
+struct cats_overlay {
+    bool enabled;              ///< which of the overlay types are enabled?
+    bool is_static;            ///< do the overlays remain constant over time?
+    int32_t reload_interval;  ///< how often do overlays change?
+    int64_t excluded_cells;
+    char *name;                ///< overlay names (as specified in the configuration file)
+    char *filename;            ///< overlay filenames or filename patterns
+    char *filename_aux;
+    char *filename_loaded;
+    void *aux_data;
+
+    struct cats_2d_array_char *data_char;
+    struct cats_2d_array_double *data_double;
+
+    cats_dt_coord cols; ///< number of rows    (same as grid, for convenience only)
+    cats_dt_coord rows; ///< number of columns (same as grid, for convenience only)
+
+};
 
 /**
  @brief Contains information for all overlays
@@ -54,19 +74,16 @@ enum ol_exclusion_type {
 struct cats_overlays {
 
         bool have_overlays;                ///< do we actually have overlays? True if at least one overlay is enabled.
-        bool enabled[OL_MAX];              ///< which of the overlay types are enabled?
-        bool is_static[OL_MAX];            ///< do the overlays remain constant over time?
-        int32_t reload_interval[OL_MAX];  ///< how often do overlays change?
-        int64_t excluded_cells;
-        char *name[OL_MAX];                ///< overlay names (as specified in the configuration file)
-        char *filename[OL_MAX];            ///< overlay filenames or filename patterns
-        char *filename_aux[OL_MAX];
-        char *filename_loaded[OL_MAX];
-        void *aux_data[OL_MAX];
-
+        struct cats_overlay overlay[OL_MAX];
 
         struct cats_2d_array_char *exclusion;
         struct cats_2d_array_double *habitat_cc;
+        struct cats_2d_array_double *resources;
+
+        struct cast_overlay *custom_overlays;
+        int32_t custom_overlay_count;
+
+        struct string_array *registered_custom_overlay_names;
 
         cats_dt_coord cols; ///< number of rows    (same as grid, for convenience only)
         cats_dt_coord rows; ///< number of columns (same as grid, for convenience only)
@@ -77,7 +94,7 @@ void cleanup_overlays(struct cats_overlays *overlays);
 
 const char *get_overlay_name(enum overlay_type type);
 
-enum overlay_type get_overlay_type_from_name(const char *name);
+enum overlay_type get_overlay_type_from_name(const char *name, const struct string_array *registered_names);
 
 
 bool
@@ -86,5 +103,5 @@ update_overlay_if_needed(struct cats_configuration *conf, int32_t time, enum ove
 void reset_overlay(struct cats_overlays *overlay);
 
 void load_overlay_from_file(struct cats_configuration *conf, enum overlay_type type, char *filename);
-
+struct cats_overlay *add_overlay(struct cats_configuration *conf, const char *name);
 #endif
\ No newline at end of file
diff --git a/src/cats/paths/directory_helper.c b/src/cats/paths/directory_helper.c
index 327518fa867c1ed86dc7f87a4125df41e125321f..11494f0d642625f0ccdaef0bb6580953fd6f5b95 100644
--- a/src/cats/paths/directory_helper.c
+++ b/src/cats/paths/directory_helper.c
@@ -59,8 +59,14 @@ struct string_array *get_needed_directories(const struct cats_configuration *con
         struct string_array *array = new_string_array();
         string_array_add(array, "adults");
         string_array_add(array, "stats");
-        string_array_add(array, "environments");
-        if (conf->write_all) {
+
+
+        if (conf->output.write_all && conf->output.have_glm) {
+                string_array_add(array, "environments");
+        }
+
+
+        if (conf->output.write_all) {
                 string_array_add(array, "seeds");
                 string_array_add(array, "juveniles");
         }
@@ -87,29 +93,36 @@ void ensure_needed_directories_exist(const struct cats_configuration *conf, cons
         log_message(LOG_INFO, "%s: Checking output directories", __func__);
 
 
-
-
-
-        struct string_array *output = new_string_array_init(conf->output_directory);
+        struct string_array *output = new_string_array_init(conf->output.output_directory);
         check_and_create_directory_if_needed(output);
         free_string_array(&output);
         for (int32_t i = 0; i < output_dirs->count; i++) {
                 struct string_array *path = get_output_directory(conf, output_dirs->string[i]);
                 check_and_create_directory_if_needed(path);
                 free_string_array(&path);
+
+
         }
+
+        free_string_array(&output);
 }
 
+void setup_module_directories(struct cats_configuration *conf)
+{
+        struct string_array *dirs_to_check = conf->output.needed_module_output_directories;
+        ensure_needed_directories_exist(conf, dirs_to_check);
+}
 
-void setup_directories(struct cats_configuration *conf)
 
-{        if (conf->command_line_options.output_directory) {
-                log_message(LOG_IMPORTANT, "overriding output directory from '%s' to '%s'", conf->output_directory,
+void setup_directories(struct cats_configuration *conf)
+{
+        if (conf->command_line_options.output_directory) {
+                log_message(LOG_IMPORTANT, "overriding output directory from '%s' to '%s'", conf->output.output_directory,
                             conf->command_line_options.output_directory);
-                free(conf->output_directory);
-                conf->output_directory = strdup(conf->command_line_options.output_directory);
+                free(conf->output.output_directory);
+                conf->output.output_directory = strdup(conf->command_line_options.output_directory);
         } else {
-                log_message(LOG_IMPORTANT, "output directory is '%s'", conf->output_directory);
+                log_message(LOG_IMPORTANT, "output directory is '%s'", conf->output.output_directory);
         }
 
         struct string_array *dirs_to_check = get_needed_directories(conf);
diff --git a/src/cats/paths/directory_helper.h b/src/cats/paths/directory_helper.h
index 1593ce568803787e080b9b04cf0e917ad6f5493a..6632bc0ea8f99db0c0523be2b475587c6c57d0bf 100644
--- a/src/cats/paths/directory_helper.h
+++ b/src/cats/paths/directory_helper.h
@@ -35,5 +35,6 @@ struct string_array *get_needed_directories(const struct cats_configuration *con
 
 bool check_and_create_directory_if_needed(const struct string_array *path);
 
+void setup_module_directories(struct cats_configuration *conf);
 
 #endif //CATS_DIRECTORY_HELPER_H
diff --git a/src/cats/paths/output_paths.c b/src/cats/paths/output_paths.c
index 0725cfece66698296ec98c52e31d6efd412d3a51..0bf139b941e1faa696768168a8dffd70a84c8490 100644
--- a/src/cats/paths/output_paths.c
+++ b/src/cats/paths/output_paths.c
@@ -36,8 +36,8 @@
 
 const char *get_base_output_directory(const struct cats_configuration *conf)
 {
-        if (conf->output_directory && strlen(conf->output_directory)) {
-                return conf->output_directory;
+        if (conf->output.output_directory && strlen(conf->output.output_directory)) {
+                return conf->output.output_directory;
         }
 
         return DEFAULT_OUTPUT_DIRECTORY;
diff --git a/src/cats/paths/path_patterns.c b/src/cats/paths/path_patterns.c
new file mode 100644
index 0000000000000000000000000000000000000000..954ac61c22389c9783d0b63030c77a522945c306
--- /dev/null
+++ b/src/cats/paths/path_patterns.c
@@ -0,0 +1,40 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * action_overlay_update.c
+ *
+ * Copyright (C) 2011-2022, University of Vienna and Vienna Institute for Nature Conservation & Analyses, Andreas Gattringer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "path_patterns.h"
+#include "temporal/timeformat.h"
+
+
+char *filename_pattern_substitution(const char *pattern, const struct cats_configuration *conf, int32_t year)
+{
+        char *time_string = formatted_year(&conf->time, year);
+        char *year_pattern = replace_substring(pattern, "%Y", time_string);
+        char *replicate_string = NULL;
+        int rc = asprintf(&replicate_string, "%d", conf->simulation.replicate);
+        asprintf_check(rc);
+        char *replicate_pattern = replace_substring(year_pattern, "%n", replicate_string);
+        free(year_pattern);
+        free(time_string);
+        free(replicate_string);
+        return replicate_pattern;
+}
\ No newline at end of file
diff --git a/src/cats/paths/path_patterns.h b/src/cats/paths/path_patterns.h
new file mode 100644
index 0000000000000000000000000000000000000000..1c81313ac67d0738a2901461329f380686ec59b0
--- /dev/null
+++ b/src/cats/paths/path_patterns.h
@@ -0,0 +1,30 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * action_overlay_update.c
+ *
+ * Copyright (C) 2011-2022, University of Vienna and Vienna Institute for Nature Conservation & Analyses, Andreas Gattringer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef CATS_PATH_PATTERNS_H
+#define CATS_PATH_PATTERNS_H
+#include "configuration/configuration.h"
+
+char *filename_pattern_substitution(const char *pattern, const struct cats_configuration *conf, int32_t year);
+
+#endif //CATS_PATH_PATTERNS_H
diff --git a/src/cats/paths/paths_suitability.c b/src/cats/paths/paths_suitability.c
index d76cb3a901d3ff6f9baa8140e291b5f165bb92c3..628823af2c8a55ddb3b6c27488163ca267cec7ed 100644
--- a/src/cats/paths/paths_suitability.c
+++ b/src/cats/paths/paths_suitability.c
@@ -27,6 +27,7 @@
 #include "environment/environment.h"
 #include "temporal/timeformat.h"
 #include "data/species_parameters.h"
+#include "path_patterns.h"
 
 
 char *
@@ -38,11 +39,7 @@ get_environment_filename(const struct cats_configuration *conf, const struct cat
 
         struct string_array *name = new_string_array();
 
-        char *pattern = NULL;
-        char *time_string = formatted_year(&conf->time, year);
-        pattern = replace_substring(environment->pattern, "%Y", time_string);
-
-        free(time_string);
+        char *pattern = filename_pattern_substitution(environment->pattern, conf, year);
 
         string_array_add(name, pattern);
         free(pattern);
diff --git a/src/cats/performance/performance_stats.c b/src/cats/performance/performance_stats.c
index 98155d10bb7e07194f3273a5bb771d7a800d25c7..9d9355a6b712466e5e4b1fb4c64d3aab0994e577 100644
--- a/src/cats/performance/performance_stats.c
+++ b/src/cats/performance/performance_stats.c
@@ -27,6 +27,6 @@
 void add_performance_info(const struct cats_configuration *conf, int year, const char *name, double time_cpu,
                           double time_wall)
 {
-        if (!conf->performance_file) return;
-        fprintf(conf->performance_file, "%d,%s,%f,%f\n", year, name, time_cpu, time_wall);
+        if (!conf->output.performance_file) return;
+        fprintf(conf->output.performance_file, "%d,%s,%f,%f\n", year, name, time_cpu, time_wall);
 }
\ No newline at end of file
diff --git a/src/cats/populations/carrying_capacity.c b/src/cats/populations/carrying_capacity.c
index f7980ed84abe6516b34a9b9da67b50e80f186f4e..a285ee516d61783de0b9c63054980ef7692b0f29 100644
--- a/src/cats/populations/carrying_capacity.c
+++ b/src/cats/populations/carrying_capacity.c
@@ -59,7 +59,7 @@ get_carrying_capacity(const struct cats_grid *grid, cats_dt_coord row, cats_dt_c
 
         double multiplier = 1.0;
 
-        if (conf->overlays.enabled[OL_HABITAT_TYPE_CC]) {
+        if (conf->overlays.overlay[OL_HABITAT_TYPE_CC].enabled) {
                 multiplier *= conf->overlays.habitat_cc->data[row][col];
                 if (multiplier <= 0.0) { return 0; }
         }
diff --git a/src/cats/stats/global_stats.c b/src/cats/stats/global_stats.c
index 2a813746a4bffeab9455e238022f12d8aa2c31d7..8c818d68ec895e0289478b7e0ab9b9adb330da4f 100644
--- a/src/cats/stats/global_stats.c
+++ b/src/cats/stats/global_stats.c
@@ -47,17 +47,15 @@ void initialize_global_stats(struct cats_configuration *conf)
         char *filename = get_global_stat_filename(conf);
         remove(filename);
 
-        conf->statsfile_global = fopen(filename, "a+");
-        ENSURE_FILE_OPENED(conf->statsfile_global, filename)
-        if (conf->summary_file) {
-                fprintf(conf->summary_file, "%s,%d,NA,global-stats,%s\n",conf->run_name, conf->simulation.replicate,filename);
-                fflush(conf->summary_file);
-        }
+
+        conf->output.statsfile_global = fopen(filename, "a+");
+        ENSURE_FILE_OPENED(conf->output.statsfile_global, filename)
+        if (conf->output.summary_file) fprintf(conf->output.summary_file, "global-stats,%s,\n",filename);
 
 
         write_global_stats(conf, NULL, true);
 
-        fflush(conf->statsfile_global);
+        fflush(conf->output.statsfile_global);
         free(filename);
 }
 
@@ -112,8 +110,8 @@ void write_global_stats(struct cats_configuration *conf, struct cats_grid *grid,
         }
 
         char *string = string_array_paste(x, ",");
-        fprintf(conf->statsfile_global, "%s", string);
-        fflush(conf->statsfile_global);
+        fprintf(conf->output.statsfile_global, "%s", string);
+        fflush(conf->output.statsfile_global);
 }
 
 
diff --git a/src/cats/stats/grid_stats.c b/src/cats/stats/grid_stats.c
index f2a290012172a6b4c5c5cdddbf0857654e174773..d018b5e83c5a32c18e97a1d43909e281c0da6dac 100644
--- a/src/cats/stats/grid_stats.c
+++ b/src/cats/stats/grid_stats.c
@@ -53,13 +53,13 @@ struct string_array *add_stats_header_overlay(struct string_array *x, struct cat
 
 
         for (enum overlay_type type = OL_NONE; type < OL_MAX; type++) {
-                if (conf->overlays.enabled[type]) {
+                if (conf->overlays.overlay[type].enabled) {
                         if (header) {
-                                char *overlay_name = compound_string("overlay", conf->overlays.name[type], "::");
+                                char *overlay_name = compound_string("overlay", conf->overlays.overlay[type].name, "::");
                                 string_array_add(x, overlay_name);
                                 free(overlay_name);
                         } else {
-                                string_array_add(x, conf->overlays.filename_loaded[type]);
+                                string_array_add(x, conf->overlays.overlay[type].filename_loaded);
                         }
                 }
         }
@@ -143,6 +143,12 @@ void consolidate_thread_stats(struct cats_grid *grid, struct cats_configuration
                         for (int32_t stat_idx = STAT_MIN; stat_idx < STAT_MAX; stat_idx++) {
                                 g->stats.stats[stat_idx] += threads[i].stats[class].stats[stat_idx];
                         }
+
+                        if (g->stats.custom_stats) {
+                                for (int64_t j = 0; j < threads[i].stats->custom_stat_count; j++){
+                                        g->stats.custom_stats[j] += threads[i].stats[class].custom_stats[j];
+                                }
+                        }
                 }
         }
 }
diff --git a/src/cats/stats/statistics.c b/src/cats/stats/statistics.c
index e0c63deafe896b2fd7a5af4446a7629654b2e2dd..d853989677488f82f2422a2950db187cf60d9b28 100644
--- a/src/cats/stats/statistics.c
+++ b/src/cats/stats/statistics.c
@@ -23,8 +23,11 @@
 
 
 #include <assert.h>
+#include <stdlib.h>
 #include "statistics.h"
 #include "cats_strings/cats_strings.h"
+#include "memory/cats_memory.h"
+#include "configuration/configuration.h"
 
 
 void zero_statistics_stats(struct statistics *stats)
@@ -36,28 +39,74 @@ void zero_statistics_stats(struct statistics *stats)
         char **has_been_populated_ts = stats->has_been_populated_ts;
         int32_t *N = stats->populated_by_classes;
         struct string_array *header = stats->stats_header;
-
+        int64_t *custom_stats = stats->custom_stats;
+        int64_t custom_stat_count = stats->custom_stat_count;
         // reset everything
         struct statistics empty = {0};
         *stats = empty;
 
+
         // restore some values
         stats->file = file;
         stats->has_been_populated = has_been_populated;
         stats->has_been_populated_ts = has_been_populated_ts;
         stats->populated_by_classes = N;
         stats->stats_header = header;
+        stats->custom_stats = custom_stats;
+        stats->custom_stat_count = custom_stat_count;
+
+        for (int32_t i = 0; i < STAT_MAX; i++) {
+                stats->stats[i] = 0;
+        }
+
+        if (stats->custom_stats != NULL) {
+                for (int64_t i = 0; i < stats->custom_stat_count; i++) {
+                        stats->custom_stats[i] = 0;
+                }
+        }
+
 }
 
 
-void init_statistics(struct statistics *stats)
+void init_statistics(struct statistics *stats, struct cats_stats_registry *registry)
 {
+        if (stats->initialized) {
+                log_message(LOG_WARNING, "statistics are already initialised");
+        }
         struct statistics empty = {0};
         *stats = empty;
+        stats->custom_stat_count = registry->count;
+        stats->custom_stats = calloc_or_die(stats->custom_stat_count, sizeof(int64_t));
+        stats->initialized = true;
 }
 
 
 void cleanup_statistics(struct statistics *stats)
 {
         if (stats->stats_header) free_string_array(&stats->stats_header);
-}
\ No newline at end of file
+        free(stats->custom_stats);
+        stats->custom_stats = NULL;
+}
+
+
+int64_t add_custom_stat(struct cats_stats_registry *registry, const char *name)
+{
+        int64_t count = registry->count;
+        string_array_add(registry->names, name);
+        registry->count += 1;
+        return count;
+}
+
+
+void init_stats_registry(struct cats_stats_registry *registry)
+{
+        registry->count = 0;
+        registry->names = new_string_array();
+}
+
+void cleanup_stats_registry(struct cats_stats_registry *registry)
+{
+        registry->count = 0;
+        free_string_array(&registry->names);
+        registry->names = NULL;
+}
diff --git a/src/cats/stats/statistics.h b/src/cats/stats/statistics.h
index df0208d8300382553d6de9d67a803014e917963c..f5f9f5b3b08f6078ff1830487c01ad45e20021f9 100644
--- a/src/cats/stats/statistics.h
+++ b/src/cats/stats/statistics.h
@@ -23,10 +23,16 @@
 
 #ifndef CATS_STATISTICS_H
 #define CATS_STATISTICS_H
-
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdint.h>
 
+struct cats_stats_registry {
+    int64_t count;
+    struct string_array *names;
+};
+
+
 // STAT_MIN and STAT_MAX are used for enumeration purposes
 // CS (CELL STATS) count the number of cells where the value of the specified stat is larger than zero
 // GS (GRID SUM) is the sum of the specified stat over the whole simulation area
@@ -69,8 +75,10 @@ enum cats_stats {
 
 /// @brief data structure for collected statistics, both on a per-grid and global basis
 struct statistics {
-
+        bool initialized;
         int64_t stats[STAT_MAX];       ///< individual cell stats, on for each \sa enum cats_stats
+        int64_t *custom_stats;
+        int64_t custom_stat_count;
         FILE *file;                    ///< file handle for output file
         int32_t *populated_by_classes; ///< only used for global stats: how many cells are populated by 1, 2, 3, ... N classes
         char **has_been_populated;     ///< which cells have already been populated at least once?
@@ -80,8 +88,9 @@ struct statistics {
 
 void zero_statistics_stats(struct statistics *stats);
 
-void init_statistics(struct statistics *stats);
+void init_statistics(struct statistics *stats, struct cats_stats_registry *registry);
 
 void cleanup_statistics(struct statistics *stats);
-
+void init_stats_registry(struct cats_stats_registry *registry);
+int64_t add_custom_stat(struct cats_stats_registry *registry, const char *name);
 #endif //CATS_STATISTICS_H
diff --git a/src/cats/stats/write_stats.c b/src/cats/stats/write_stats.c
index b4e440dad9041af9531419b56420ff0d82a75fd9..eb80e40d7368f28f39532422f8d5a19fb3b9dac6 100644
--- a/src/cats/stats/write_stats.c
+++ b/src/cats/stats/write_stats.c
@@ -35,6 +35,8 @@
 
 const char *get_stat_name(enum cats_stats which)
 {
+
+
         switch (which) {
                 case CS_POPULATED:
                         return "populated";
@@ -155,7 +157,7 @@ void gui_output_grid_stats(const struct string_array *header, const struct strin
 void write_grid_stats(struct cats_configuration *conf, struct cats_grid *grid, bool header)
 {
 
-        if (conf->performance_file && grid->id == 0) fflush(conf->performance_file);
+        if (conf->output.performance_file && grid->id == 0) fflush(conf->output.performance_file);
 
         if (grid->stats.file == NULL) {
                 log_message(LOG_ERROR,
diff --git a/src/cats/threading/threading-helpers.c b/src/cats/threading/threading-helpers.c
index 653006e2696072790bbde6ff99c920023d8ca0ae..0160f610d0f5a991342862f8bacdf688a7e2555f 100644
--- a/src/cats/threading/threading-helpers.c
+++ b/src/cats/threading/threading-helpers.c
@@ -30,6 +30,13 @@
 #include "misc/cats_random.h"
 #include "stats/grid_stats.h"
 #include "data/error.h"
+#include "memory/cats_memory.h"
+
+void create_custom_stats_for_thread(struct statistics *thread_stats, struct statistics *grid_stats)
+{
+        thread_stats->custom_stat_count = grid_stats->custom_stat_count;
+        thread_stats->custom_stats = calloc_or_die(grid_stats->custom_stat_count, sizeof(int64_t));
+}
 
 void
 initialize_thread(struct cats_thread_info *thread, struct cats_grid *grid, struct cats_configuration *conf, int32_t id)
@@ -37,14 +44,27 @@ initialize_thread(struct cats_thread_info *thread, struct cats_grid *grid, struc
         thread->rng = allocate_rng();
         thread->rng_seed = conf->rng_seed;
 
-        for (int32_t i = 0; i < MAX_CLASSES; i++) {
+        for (int32_t i = 0; i < conf->grid_count; i++) {
+
+                create_custom_stats_for_thread(&thread->stats[i], &grid->stats);
                 zero_statistics_stats(&thread->stats[i]);
         }
 
+        thread->rw_debug_cells_with_adults = 0;
+        thread->rw_debug_random_walks = 0;
+        thread->rw_debug_deposits = 0;
+        thread->seed = get_random_seed(false);
 
+        for (int i = 0; i < 4; i++) {
+                thread->seed4[i] = get_random_seed64(false);
+        }
         thread->id = id;
         thread->grid = grid;
         thread->conf = conf;
+        thread->rng_buf_size = 32;
+        thread->rng_state_buffer = calloc_or_die(1, thread->rng_buf_size);
+        thread->rng_state = calloc_or_die(1, sizeof(struct random_data));
+        initstate_r(get_random_seed(false), thread->rng_state_buffer, thread->rng_buf_size, thread->rng_state);
 }
 
 
@@ -56,12 +76,22 @@ int32_t get_dispersal_radius(const struct cats_configuration *conf, int32_t id)
 
 void cleanup_threads(struct cats_thread_info *threads, int32_t num_threads)
 {
+
         for (int32_t i = 0; i < num_threads; i++) {
+
                 gsl_rng_free(threads[i].rng);
+                free(threads[i].rng_state);
+                free(threads[i].rng_state_buffer);
         }
 
-
+        for (int32_t i = 0; i < num_threads; i++) {
+                for (int32_t j = 0; j < threads[i].conf->grid_count; j++) {
+                        free(threads[i].stats->custom_stats);
+                }
+        }
         free(threads);
+
+
 }
 
 
diff --git a/src/cats/threading/threading.c b/src/cats/threading/threading.c
index e593f2c302f4f403039f6595adefea93bad99d37..539fd2b938190e14846a3264f422c9fcae8cd052 100644
--- a/src/cats/threading/threading.c
+++ b/src/cats/threading/threading.c
@@ -94,6 +94,8 @@ setup_threads(struct cats_configuration *conf, struct cats_grid *grid, int32_t m
         }
 
 
+
+
         switch (t->strategy) {
                 case TS_UNKNOWN:
                         log_message(LOG_ERROR, "undefined threading strategy");
diff --git a/src/cats/threading/threading.h b/src/cats/threading/threading.h
index 3a15267ab6692fa37bc64d428f560bc8dbc2609d..b34f2a2a9e462b4db9f15a0198218b4a8c2f1b3b 100644
--- a/src/cats/threading/threading.h
+++ b/src/cats/threading/threading.h
@@ -50,6 +50,14 @@ struct cats_thread_info {
         struct cats_configuration *conf;
         gsl_rng *rng;
         unsigned int rng_seed;
+        int64_t rw_debug_cells_with_adults;
+        int64_t rw_debug_random_walks;
+        int64_t rw_debug_deposits;
+        char *rng_state_buffer;
+        int32_t rng_buf_size;
+        struct random_data *rng_state;
+        unsigned int seed;
+        uint64_t seed4[4];
 
 };
 
diff --git a/src/cats/vital_rates/setup_rates.c b/src/cats/vital_rates/setup_rates.c
index 6e3279338b622a125d8cb4e3b89c0ac8e746277e..29d7585f62c448e6f83a4faa64fdd56830513f59 100644
--- a/src/cats/vital_rates/setup_rates.c
+++ b/src/cats/vital_rates/setup_rates.c
@@ -109,12 +109,32 @@ void set_vital_rate_maximum(struct cats_vital_rate *vital, cats_dt_rates max_rat
 }
 
 
+void set_vital_rate_minimum(struct cats_vital_rate *vital, cats_dt_rates minimum)
+{
+        assert(vital != NULL);
+        if (minimum < 0) {
+                log_message(LOG_ERROR, "%s (%s): vital rate minimum %Lf must not be negative ", __func__,
+                            vital->name, minimum);
+                exit_cats(EXIT_FAILURE);
+        }
+
+        if (minimum > vital->max_rate) {
+                log_message(LOG_ERROR, "%s (%s): vital rate minimum %Lf must be less or equal to maximum %Lf "
+                            , __func__, vital->name, minimum, vital->max_rate);
+                exit_cats(EXIT_FAILURE);
+        }
+
+        vital->min_rate = minimum;
+
+}
+
 void set_vital_density(struct cats_vital_rate *vital, enum cats_density_dependence density)
 {
         assert(vital != NULL);
         if (vital->is_carrying_capacity && density != NO_DENSITY_DEP) {
-                log_message(LOG_ERROR, "%s (%s): carrying capacity may never depend on population density", __func__,
-                            vital->name);
+
+                log_message(LOG_ERROR, "%s (%s): carrying capacity may never depend on population density", __func__, vital->name);
+
                 exit_cats(EXIT_FAILURE);
         }
         vital->density = density;
diff --git a/src/cats/vital_rates/setup_rates.h b/src/cats/vital_rates/setup_rates.h
index f700b88f70d11d30e091675888ada24763859c03..3ea94f64c815026bb9eb4874fd6ca8f2e971a71f 100644
--- a/src/cats/vital_rates/setup_rates.h
+++ b/src/cats/vital_rates/setup_rates.h
@@ -45,7 +45,7 @@ void set_vital_density(struct cats_vital_rate *vital, enum cats_density_dependen
 
 void load_vital_rates(struct cats_configuration *conf);
 
-void set_vital_cutoff(struct cats_vital_rate *vital, const cats_dt_rates value,
+void set_vital_cutoff(struct cats_vital_rate *vital, cats_dt_rates value,
                       const struct cats_species_param *param);
 
 enum cats_rate_function_type get_function_type(const struct cats_configuration *conf, const char *short_name);
@@ -60,4 +60,6 @@ void set_vital_rate_link_hybrid_function(struct cats_vital_rate *vr, struct cats
 
 void set_vital_density(struct cats_vital_rate *vital, enum cats_density_dependence density);
 
+void set_vital_rate_minimum(struct cats_vital_rate *vital, cats_dt_rates minimum);
+
 #endif //CATS_SETUP_RATES_H
diff --git a/src/modules/butterflies/CMakeLists.txt b/src/modules/butterflies/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c20a250845e6cfd490a47a0312602fc2f7ded1c1
--- /dev/null
+++ b/src/modules/butterflies/CMakeLists.txt
@@ -0,0 +1,27 @@
+add_library(cats-butterflies SHARED ""
+            butterflies_actions.c butterflies_actions.h
+            butterflies_vital_rates.c butterflies_vital_rates.h
+            module.h
+            butterflies_dispersal.c butterflies_dispersal.h
+            butterflies_populations.c butterflies_populations.h
+            butterflies_inline.h
+            butterflies_generations.c butterflies_generations.h
+            butterflies_stats.c
+            butterflies_overlays.c butterflies_overlays.h
+            butterflies_paths.c butterflies_paths.h
+            butterflies_actions_setup.c
+            butterflies_actions_setup.h butterflies_initial_population.c butterflies_initial_population.h butterflies_scale.c butterflies_scale.h)
+
+target_include_directories(cats-butterflies PUBLIC ".")
+
+target_sources(cats-butterflies
+        PRIVATE
+        butterflies_main.c butterflies_main.h
+
+        PUBLIC
+
+        )
+
+
+set_property(TARGET cats-butterflies PROPERTY POSITION_INDEPENDENT_CODE ON)
+target_link_libraries(cats-butterflies cats_logging libcats)
diff --git a/src/modules/butterflies/butterflies_actions.c b/src/modules/butterflies/butterflies_actions.c
new file mode 100644
index 0000000000000000000000000000000000000000..4ce07bee845a5f15064bc28020a6b691ba213199
--- /dev/null
+++ b/src/modules/butterflies/butterflies_actions.c
@@ -0,0 +1,360 @@
+#include "actions/cats_actions.h"
+#include "modules/module_header.h"
+#include "butterflies_actions.h"
+#include "butterflies_main.h"
+#include "inline_overlays.h"
+#include "butterflies_populations.h"
+
+#include "butterflies_inline.h"
+#include "grids/grid_wrapper.h"
+#include "grids/gdal_save.h"
+#include "paths/paths.h"
+#include "populations/population.h"
+#include "butterflies_generations.h"
+#include "butterflies_dispersal.h"
+#include "butterflies_overlays.h"
+#include "butterflies_paths.h"
+#include "inline.h"
+#include "lambda/leslie_matrix.h"
+#include "temporal/years.h"
+#include "butterflies_initial_population.h"
+
+
+enum action_status bf_action_stats_reset(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        int module_id = CATS_MODULE_ID;
+
+        struct grid_data_butterflies *module_data = grid->grid_modules[module_id].module_data;
+        if (conf->time.year_current == conf->time.year_start &&
+            module_conf->generations_max == module_data->generation_current) {
+                log_message(LOG_INFO, "SUMMARY: Scale factor: %Lf", grid->param.scale_factor);
+                struct lambda_parameters l_param = {0};
+                l_param.calculate_scale = true;
+                l_param.suitability = grid->param.OT;
+                l_param.N = 0;
+                l_param.K = (cats_dt_population)
+                        (get_vital_rate_maximum(&conf->param[grid->id].carrying_capacity) *
+                         conf->param->max_adult_cc_fraction);
+                l_param.grid = 0;
+                l_param.row = 0;
+                l_param.col = 0;
+                l_param.param = &conf->param[grid->id];
+                l_param.species_id = grid->id;
+                bool print_rate = false;
+                cats_dt_rates female_fraction = module_conf->female_fraction;
+                cats_dt_rates stationary = module_conf->probability_to_stay;
+                cats_dt_rates mobile = 1.0 - stationary;
+                cats_dt_rates egg_fraction_source = module_conf->egg_fraction_source;
+                cats_dt_rates eggs_per_female = calculate_rate_for_matrix(&module_conf->eggs_per_female, &l_param,
+                                                                          print_rate);
+                cats_dt_rates reproduction_rate = calculate_rate_for_matrix(&module_conf->reproduction_rate, &l_param,
+                                                                            print_rate);
+                cats_dt_rates K =
+                        calculate_rate_for_matrix(&conf->param[grid->id].carrying_capacity, &l_param, print_rate) *
+                        conf->param->max_adult_cc_fraction;
+                cats_dt_rates local_eggs = (stationary + mobile * egg_fraction_source) * eggs_per_female;
+                // female -> female
+                // to achieve the target reproduction rate, the number of eggs per female laid in the cell
+                // that survive and become adult has to be the reproduction rate divided by the female fraction divided by the number of eggs
+                cats_dt_rates eggs_to_adults_rate =
+                        bf_egg_to_adult_survival_rate(reproduction_rate, local_eggs) / module_conf->female_fraction;
+                cats_dt_rates result = local_eggs * eggs_to_adults_rate * female_fraction;
+                cats_dt_rates generations = calculate_rate_for_matrix(&module_conf->butterfly_generations, &l_param,
+                                                                      print_rate);
+
+
+                log_message(LOG_INFO, "SUMMARY: reproduction rate at OT: %Lf", reproduction_rate);
+                log_message(LOG_INFO, "SUMMARY: eggs per female at OT: %Lf", eggs_per_female);
+                log_message(LOG_INFO, "SUMMARY: local eggs at OT: %Lf", local_eggs);
+                log_message(LOG_INFO, "SUMMARY: stationary females at OT: %Lf", stationary);
+                log_message(LOG_INFO, "SUMMARY: eggs to adult rate at OT: %Lf", eggs_to_adults_rate);
+                log_message(LOG_INFO, "SUMMARY: egg fraction source (non-stationary females) at OT: %Lf",
+                            egg_fraction_source);
+                log_message(LOG_INFO, "SUMMARY: carrying capacity at OT: %Lf", K);
+                log_message(LOG_INFO, "SUMMARY: generations at OT: %Lf", generations);
+                log_message(LOG_INFO, "SUMMARY: effective female to female rate at OT: %Lf\n", result);
+
+                l_param.suitability = 1.0;
+                eggs_per_female = calculate_rate_for_matrix(&module_conf->eggs_per_female, &l_param, print_rate);
+                reproduction_rate = calculate_rate_for_matrix(&module_conf->reproduction_rate, &l_param, print_rate);
+                K = calculate_rate_for_matrix(&conf->param[grid->id].carrying_capacity, &l_param, print_rate) *
+                    conf->param->max_adult_cc_fraction;
+                local_eggs = (stationary + mobile * egg_fraction_source) * eggs_per_female;
+                eggs_to_adults_rate =
+                        bf_egg_to_adult_survival_rate(reproduction_rate, local_eggs) / module_conf->female_fraction;
+                result = local_eggs * eggs_to_adults_rate;
+                generations = calculate_rate_for_matrix(&module_conf->butterfly_generations, &l_param, print_rate);
+
+                log_message(LOG_INFO, "SUMMARY: reproduction rate at suitability 1: %Lf", reproduction_rate);
+                log_message(LOG_INFO, "SUMMARY: eggs per female at suitability 1: %Lf", eggs_per_female);
+                log_message(LOG_INFO, "SUMMARY: local eggs at suitability 1: %Lf", local_eggs);
+                log_message(LOG_INFO, "SUMMARY: stationary females at suitability 1: %Lf", stationary);
+                log_message(LOG_INFO, "SUMMARY: eggs to adult rate at suitability 1: %Lf", eggs_to_adults_rate);
+                log_message(LOG_INFO, "SUMMARY: egg fraction source (non-stationary females) at suitability 1: %Lf",
+                            egg_fraction_source);
+                log_message(LOG_INFO, "SUMMARY: carrying capacity at suitability 1: %Lf", K);
+                log_message(LOG_INFO, "SUMMARY: generations at suitability 1: %Lf", generations);
+                log_message(LOG_INFO, "SUMMARY: effective female to female rate at suitability 1: %Lf\n", result);
+
+        }
+
+        return action_grid_stats_reset(grid, conf);
+}
+
+
+void grid_butterflies_maturation(struct cats_grid *grid, struct cats_thread_info *ts)
+{
+        struct cats_configuration *conf = ts->conf;
+        const cats_dt_coord start_row = ts->area.start_row;
+        const cats_dt_coord end_row = ts->area.end_row;
+
+        const cats_dt_coord start_col = ts->area.start_col;
+        const cats_dt_coord end_col = ts->area.end_col;
+
+        ts->rw_debug_cells_with_adults = 0;
+
+        for (cats_dt_coord row = start_row; row < end_row; row++) {
+                for (cats_dt_coord col = start_col; col < end_col; col++) {
+#ifdef BF_DEBUG
+                        if (get_adult_population(grid, row, col)) {
+                                printf("BFDBG::%s::adults at maturation::%d,%d,%d\n",
+                                       __func__, row, col, get_adult_population(grid, row, col));
+                        }
+#endif
+                        if (cell_excluded_by_overlay(conf, row, col)
+                            || bf_cell_excluded_by_generation(grid, row, col)) {
+#ifdef BF_DEBUG
+                                if (get_adult_population(grid, row, col)) {
+                                        printf("BFDBG::%s::adults at maturation::%d,%d,%d - excluded by generation\n",
+                                               __func__, row, col, get_adult_population(grid, row, col));
+                                }
+#endif
+
+                                continue;
+                        }
+
+                        bf_cell_maturation(grid, ts, row, col, false);
+                }
+        }
+
+}
+
+
+void bf_initial_population_to_eggs(struct cats_grid *grid, struct cats_thread_info *ts)
+{
+        struct cats_configuration *conf = ts->conf;
+        const cats_dt_coord start_row = ts->area.start_row;
+        const cats_dt_coord end_row = ts->area.end_row;
+
+        const cats_dt_coord start_col = ts->area.start_col;
+        const cats_dt_coord end_col = ts->area.end_col;
+
+
+        for (cats_dt_coord row = start_row; row < end_row; row++) {
+                for (cats_dt_coord col = start_col; col < end_col; col++) {
+
+                        if (cell_excluded_by_overlay(conf, row, col)) {
+                                continue;
+                        }
+                        set_population(grid, row, col, get_adult_population(grid, row, col));
+                        butterflies_cell_dispersal(grid, ts, row, col, false, true);
+                }
+        }
+
+}
+
+
+void butterflies_area_dispersal(struct cats_grid *grid, struct cats_thread_info *ts)
+{
+        struct cats_configuration *conf = ts->conf;
+        const cats_dt_coord start_row = ts->area.start_row;
+        const cats_dt_coord end_row = ts->area.end_row;
+
+        const cats_dt_coord start_col = ts->area.start_col;
+        const cats_dt_coord end_col = ts->area.end_col;
+
+        ts->rw_debug_cells_with_adults = 0;
+        ts->rw_debug_random_walks = 0;
+        ts->rw_debug_deposits = 0;
+
+
+        for (cats_dt_coord row = start_row; row < end_row; row++) {
+                for (cats_dt_coord col = start_col; col < end_col; col++) {
+                        if (cell_excluded_by_overlay(conf, row, col)
+                            || bf_cell_excluded_by_generation(grid, row, col)) {
+                                continue;
+                        }
+
+                        butterflies_cell_dispersal(grid, ts, row, col, false, false);
+                }
+        }
+
+        /*
+        if (ts->rw_debug_cells_with_adults) {
+                log_message(LOG_INFO, "thread %d: %ld cells with adults, %ld random walks, %ld deposits",
+                            ts->id, ts->rw_debug_cells_with_adults, ts->rw_debug_random_walks, ts->rw_debug_deposits);
+        }
+         */
+
+}
+
+
+enum action_status bf_action_maturation(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        threaded_action(&grid_butterflies_maturation, grid, conf, TS_DEFAULT);
+
+        return ACTION_RUN;
+}
+
+
+enum action_status bf_action_save_grid(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        if (!is_output_year(&conf->time)) return ACTION_NOT_RUN;
+        int32_t id = grid->id;
+        char *filename = bf_population_filename(conf, grid);
+        struct grid_wrapper data = gridwrapper(grid->population, grid->dimension);
+        save_grid_to_gdal(&data, GDT_Int32, conf, filename, conf->param[id].species_name);
+        free(filename);
+        return ACTION_RUN;
+}
+
+
+enum action_status bf_action_save_overlay(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        if (!is_output_year(&conf->time)) return ACTION_NOT_RUN;
+        int32_t id = grid->id;
+
+        char *filename = bf_population_overlay_filename(conf, grid);
+        int module_id = CATS_MODULE_ID;
+
+        struct grid_data_butterflies *module_data = grid->grid_modules[module_id].module_data;
+
+        struct grid_wrapper data = gridwrapper(module_data->info_layer, grid->dimension);
+        save_grid_to_gdal(&data, GDT_Int32, conf, filename, conf->param[id].species_name);
+        free(filename);
+        return ACTION_RUN;
+}
+
+
+enum action_status bf_action_save_eggs_grid(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        if (!is_output_year(&conf->time)) return ACTION_NOT_RUN;
+        int32_t id = grid->id;
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *module_data = grid->grid_modules[module_id].module_data;
+        char *filename = bf_population_eggs_filename(conf, grid);
+        struct grid_wrapper data = gridwrapper(module_data->eggs, grid->dimension);
+        save_grid_to_gdal(&data, GDT_Int32, conf, filename, conf->param[id].species_name);
+        free(filename);
+        return ACTION_RUN;
+}
+
+
+enum action_status bf_action_dispersal(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        threaded_action(&butterflies_area_dispersal, grid, conf, TS_DISPERSAL);
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+
+        for (enum butterfly_stats which = BF_POPULATED_AT_DISPERSAL; which < BF_STAT_MAX; which++) {
+                int64_t stat_id = module_conf->stat_ids[which];
+                log_message(LOG_INFO, "STAT %s: %ld", bf_get_stats_field_name(which), grid->stats.stats[stat_id]);
+        }
+
+
+        return ACTION_RUN;
+}
+
+
+enum action_status bf_action_stats_gather(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        threaded_action(&bf_area_stats_gather, grid, conf, TS_DEFAULT);
+        return ACTION_RUN;
+}
+
+
+enum action_status bf_action_stats_write(__attribute__((unused)) struct cats_grid *grid,
+                                         __attribute__((unused)) struct cats_configuration *conf)
+{
+        bf_stats_write(conf, grid);
+
+        return ACTION_RUN;
+}
+
+
+// only run at the start of the year
+enum action_status bf_action_generation_update(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        const int64_t egg_cells_id = module_conf->stat_ids[BF_CELLS_WITH_EGGS];
+        const int64_t egg_cells_removed_id = module_conf->stat_ids[BF_CELLS_WITH_EGGS_REMOVED];
+        grid->stats.custom_stats[egg_cells_id] = 0;
+        grid->stats.custom_stats[egg_cells_removed_id] = 0;
+
+        data->generation_current = module_conf->generations_max;
+        log_message(LOG_IMPORTANT, "resetting generation to %d", module_conf->generations_max);
+
+        if (grid->param.initial_population.adjusted == false) {
+                bf_initial_population_adjustment(conf, grid);
+                grid->param.initial_population.adjusted = true;
+        }
+
+        threaded_action(&bf_area_generation_update, grid, conf, TS_DEFAULT);
+
+        int64_t cells_with_eggs = grid->stats.custom_stats[egg_cells_id];
+        int64_t cells_with_eggs_removed = grid->stats.custom_stats[egg_cells_removed_id];
+        log_message(LOG_INFO, "%ld cells with eggs left, %ld cells with eggs removed", cells_with_eggs,
+                    cells_with_eggs_removed);
+        grid->stats.custom_stats[egg_cells_id] = 0;
+        grid->stats.custom_stats[egg_cells_removed_id] = 0;
+        return ACTION_RUN;
+}
+
+
+enum action_status bf_action_overlay_update(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        return bf_grid_overlay_update(conf, grid);
+}
+
+
+enum action_status bf_action_generation_finish(struct cats_grid *grid, struct cats_configuration *conf)
+{
+        const int module_id = CATS_MODULE_ID;
+
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+        threaded_action(&bf_area_kill_adults, grid, conf, TS_DEFAULT);
+
+
+        data->generation_current--;
+        assert(data->generation_current >= 0);
+        // struct conf_data_butterflies *data = CATS_MODULE_DATA;
+        return ACTION_RUN;
+}
+
+
+enum action_status
+bf_action_generation_start(struct cats_grid *grid, __attribute__((unused)) struct cats_configuration *conf)
+{
+#ifdef BF_DEBUG
+        cats_dt_population rows = grid->dimension.rows;
+        cats_dt_population cols = grid->dimension.cols;
+        for (cats_dt_coord row = 0; row < rows; row++) {
+                for (cats_dt_coord col = 0; col < cols; col++) {
+                        if (get_adult_population(grid, row, col)) {
+                                printf("BFDBG::%s::adults at start of generation::%d,%d,%d\n",
+                                       __func__, row, col, get_adult_population(grid, row, col));
+
+                        }
+                }
+        }
+#endif
+        int module_id = CATS_MODULE_ID;
+
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+        log_message(LOG_INFO, "Starting generation %d", data->generation_current);
+        assert(data->generation_current >= 0);
+        return ACTION_RUN;
+}
diff --git a/src/modules/butterflies/butterflies_actions.h b/src/modules/butterflies/butterflies_actions.h
new file mode 100644
index 0000000000000000000000000000000000000000..139efe614e8d1a1a7a9021f6172507ef1ea7e99a
--- /dev/null
+++ b/src/modules/butterflies/butterflies_actions.h
@@ -0,0 +1,30 @@
+#ifndef CATS_BUTTERFLIES_ACTIONS_H
+#define CATS_BUTTERFLIES_ACTIONS_H
+
+#include "configuration/configuration.h"
+
+enum action_status bf_action_maturation(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_save_grid(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_dispersal(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_stats_gather(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_stats_reset(__attribute__((unused)) struct cats_grid *grid, __attribute__((unused)) struct cats_configuration *conf);
+
+enum action_status bf_action_stats_write(__attribute__((unused)) struct cats_grid *grid, __attribute__((unused)) struct cats_configuration *conf);
+
+enum action_status bf_action_overlay_update(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_generation_finish(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_generation_start(struct cats_grid *grid, __attribute__((unused)) struct cats_configuration *conf);
+
+enum action_status bf_action_generation_update(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_save_eggs_grid(struct cats_grid *grid, struct cats_configuration *conf);
+
+enum action_status bf_action_save_overlay(struct cats_grid *grid, struct cats_configuration *conf);
+
+#endif //CATS_BUTTERFLIES_ACTIONS_H
diff --git a/src/modules/butterflies/butterflies_actions_setup.c b/src/modules/butterflies/butterflies_actions_setup.c
new file mode 100644
index 0000000000000000000000000000000000000000..9258f0ffaa45de1ff829a6faeab1329470d4db84
--- /dev/null
+++ b/src/modules/butterflies/butterflies_actions_setup.c
@@ -0,0 +1,78 @@
+
+#include "butterflies_actions_setup.h"
+#include "configuration/configuration.h"
+#include "actions/setup_actions.h"
+#include "module.h"
+#include "modules/module_header.h"
+#include "butterflies_actions.h"
+#include "butterflies_main.h"
+
+void bf_add_generation_action(struct cats_configuration *conf, action_function function, const char *name,
+                              int generation)
+{
+        char *result = NULL;
+        int rc = asprintf(&result, "%s (generation %d)", name, generation);
+        asprintf_check(rc);
+        append_action(conf, function, ALL_STAGES, result, module_name);
+        free(result);
+}
+
+void bf_register_actions(struct cats_configuration *conf)
+{
+
+        register_action_function(conf, bf_action_stats_reset, "butterfly_action_reset_stats",
+                                 "resetting butterfly statistics");
+        register_action_function(conf, bf_action_generation_update, "butterfly_action_update_generations",
+                                 "update generations");
+
+        register_action_function(conf, bf_action_generation_finish, "bf_action_generation_finish",
+                                 "update generation");
+        register_action_function(conf, bf_action_generation_start, "bf_action_generation_start",
+                                 "start generation");
+
+
+        register_action_function(conf, bf_action_maturation, "butterfly_action_egg_to_adult",
+                                 "transition eggs to adults");
+
+        register_action_function(conf, bf_action_overlay_update, "butterfly_action_overlay_update",
+                                 "updating overlays");
+
+        register_action_function(conf, bf_action_dispersal, "butterfly_action_dispersal", "egg dispersal");
+        register_action_function(conf, bf_action_save_grid, "bf_action_save_grid", "output");
+        register_action_function(conf, bf_action_save_eggs_grid, "bf_action_save_egg_grid", "egg output");
+        register_action_function(conf, bf_action_save_overlay, "bf_action_save_overlay_grid", "overlay output");
+        register_action_function(conf, bf_action_stats_gather, "bf_action_stats_gather", "gather stats");
+        register_action_function(conf, bf_action_stats_reset, "bf_action_stats_reset", "reset stats");
+        register_action_function(conf, bf_action_stats_write, "bf_action_stats_write", "write stats");
+}
+
+
+void bf_add_actions(struct cats_configuration *conf)
+{
+        struct conf_data_butterflies *data = CATS_MODULE_DATA;
+
+        log_message(LOG_INFO, "Adding actions of %d generations", data->generations_max);
+
+
+        append_action(conf, bf_action_stats_reset, ALL_STAGES, "resetting butterfly statistics", module_name);
+        append_action_by_name(conf, "action_load_environments", ALL_STAGES, "environment update", module_name);
+        append_action_by_name(conf, "action_overlay_update", ALL_STAGES, "overlay update", module_name);
+
+        append_action(conf, bf_action_overlay_update, ALL_STAGES, "updating resource layer", module_name);
+        append_action(conf, bf_action_generation_update, ALL_STAGES, "update generations", module_name);
+        append_action(conf, bf_action_save_overlay, ALL_STAGES, "save overlay", module_name);
+
+        for (int32_t generation = data->generations_max; generation > 0; generation--) {
+
+                bf_add_generation_action(conf, bf_action_stats_reset, "reset stats", generation);
+                bf_add_generation_action(conf, bf_action_generation_start, "start generation", generation);
+                bf_add_generation_action(conf, bf_action_maturation, "transition eggs to adults",
+                                         generation);
+                bf_add_generation_action(conf, bf_action_save_grid, "output adults", generation);
+                bf_add_generation_action(conf, bf_action_stats_gather, "gather stats", generation);
+                bf_add_generation_action(conf, bf_action_dispersal, "dispersal", generation);
+                bf_add_generation_action(conf, bf_action_save_eggs_grid, "output eggs", generation);
+                bf_add_generation_action(conf, bf_action_generation_finish, "finish generation", generation);
+                bf_add_generation_action(conf, bf_action_stats_write, "write stats", generation);
+        }
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_actions_setup.h b/src/modules/butterflies/butterflies_actions_setup.h
new file mode 100644
index 0000000000000000000000000000000000000000..224068527270fadfbef4b2e9a312283468c5a7ab
--- /dev/null
+++ b/src/modules/butterflies/butterflies_actions_setup.h
@@ -0,0 +1,9 @@
+#ifndef CATS_BUTTERFLIES_ACTIONS_SETUP_H
+#define CATS_BUTTERFLIES_ACTIONS_SETUP_H
+
+#include "configuration/configuration.h"
+
+void bf_register_actions(struct cats_configuration *conf);
+void bf_add_actions(struct cats_configuration *conf);
+
+#endif //CATS_BUTTERFLIES_ACTIONS_SETUP_H
diff --git a/src/modules/butterflies/butterflies_dispersal.c b/src/modules/butterflies/butterflies_dispersal.c
new file mode 100644
index 0000000000000000000000000000000000000000..b995e3dab695c13bf7998b6dba32c35602ee734f
--- /dev/null
+++ b/src/modules/butterflies/butterflies_dispersal.c
@@ -0,0 +1,228 @@
+#include "inline_population.h"
+#include "butterflies_inline.h"
+#include "misc/cats_random.h"
+#include "populations/carrying_capacity.h"
+#include "inline_overlays.h"
+
+#include <math.h>
+#include <gsl/gsl_randist.h>
+#include "butterflies_main.h"
+#include "butterflies_dispersal.h"
+#include "configuration/configuration.h"
+#include "modules/module_header.h"
+
+const int N_DIRECTIONS = 9;
+/*
+ *  directions
+ *   4  3  2
+ *   5  0  1
+ *   6  7  8
+ */
+
+const cats_dt_coord DIRECTION_OFFSETS[9][2] = {
+        {+0, +0},
+        {+0, +1},
+        {-1, +1},
+        {-1, +0},
+        {-1, -1},
+        {+0, -1},
+        {+1, -1},
+        {+1, +0},
+        {+1, +1}
+};
+
+
+static void inline single_random_walk(struct cats_thread_info *ts, struct cats_grid *grid, cats_dt_coord source_row,
+                                      cats_dt_coord source_col, int32_t eggs, cats_dt_rates egg_fraction_step,
+                                      __attribute__((unused)) int32_t rw_num)
+{
+
+        const int module_id = CATS_MODULE_ID;
+        const struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+        const struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        const bool debug_rw = module_conf->debug_rw;
+
+
+        int32_t eggs_left = eggs;
+        const cats_dt_coord max_steps = module_conf->animal_dispersal_max_radius;
+
+
+        cats_dt_coord row = source_row;
+        cats_dt_coord col = source_col;
+
+        const cats_dt_coord rows = grid->dimension.rows;
+        const cats_dt_coord cols = grid->dimension.cols;
+
+
+        for (cats_dt_coord step = 0; step < max_steps; step++) {
+                const unsigned long int direction = gsl_rng_uniform_int(ts->rng, N_DIRECTIONS);
+
+
+                const cats_dt_coord *offsets = DIRECTION_OFFSETS[direction];
+                const cats_dt_coord row_offset = offsets[0];
+                const cats_dt_coord col_offset = offsets[1];
+
+                assert(row_offset >= -1 && row_offset <= 1);
+                assert(col_offset >= -1 && col_offset <= 1);
+
+                row += row_offset;
+                col += col_offset;
+
+                if (row >= rows || row < 0 || col >= cols || col < 0) {
+                        return; // we escaped the simulation extent and got lost
+                }
+
+
+                // is the cell a valid dispersal target location?
+                if (!(data->info_layer[row][col] & BF_CELL_VALID_DISPERSAL_TARGET)) {
+                        if (debug_rw) {
+                                fprintf(module_conf->debug_rw_file, "%d,%d,%d,%d,%d,%d,%d\n", rw_num, row, col,
+                                        step + 1,
+                                        module_conf->animal_dispersal_max_radius - step - 1, 0, eggs_left);
+                        }
+
+                        continue;
+                }
+
+
+                int32_t eggs_to_deposit = (int32_t) ceilf((float) (eggs_left * egg_fraction_step));
+                assert(eggs_to_deposit >= 0);
+                //printf("x %d %d -> %d %d: %d of %d / %Lf\n", source_row, source_col, row, col, eggs_to_deposit, eggs, egg_fraction_step);
+                if (eggs_to_deposit > eggs_left) {
+                        eggs_to_deposit = eggs_left;
+                }
+
+                eggs_left -= eggs_to_deposit;
+                data->eggs[row][col] += (float) eggs_to_deposit;
+                ts->rw_debug_deposits++;
+                if (debug_rw) {
+                        fprintf(module_conf->debug_rw_file, "%d,%d,%d,%d,%d,%d,%d\n", rw_num, row, col, step + 1,
+                                module_conf->animal_dispersal_max_radius - step - 1, eggs_to_deposit, eggs_left);
+                }
+
+
+                if (eggs_left == 0) break;
+
+        }
+
+        assert(eggs_left >= 0);
+}
+
+
+
+
+
+
+void
+butterflies_cell_dispersal(struct cats_grid *grid, struct cats_thread_info *ts, cats_dt_coord row, cats_dt_coord col,
+                           bool check_exclusion, bool local_only)
+{
+        const struct cats_configuration *conf = ts->conf;
+        if (check_exclusion
+            && (cell_excluded_by_overlay(conf, row, col) || bf_cell_excluded_by_generation(grid, row, col)))
+                return;
+
+
+        // total adults: the number of adults that became adult in this cell, possibly exceeding the carrying capacity (thanks to poisson processes)
+        const cats_dt_population total_adults = get_adult_population(grid, row, col);
+
+        if (total_adults == 0) return;
+        ts->rw_debug_cells_with_adults++;
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        // total females: how many of the total adults are female, as drawn from a binomial distribution with p = 0.5
+        // can be safely cast, because gsl_ran_binomial will return a number <= total_adults
+        const cats_dt_population total_females = (cats_dt_population) gsl_ran_binomial(ts->rng,
+                                                                                       (double) module_conf->female_fraction,
+                                                                                       total_adults);
+        assert(total_females >= 0 && total_females <= total_adults);
+
+        // total males: the rest
+        const cats_dt_population total_males = total_adults - total_females;
+
+        // we need at least one female and one male
+        if (total_females == 0 || total_males == 0) return;
+
+        // here we calculate the number of eggs per female, so we can return early if the cell is unsuitable
+
+        cats_dt_rates eggs_per_f = calculate_rate(&module_conf->eggs_per_female, total_adults, &grid->param, grid, row,
+                                                  col, NULL);
+        int32_t eggs_per_female = (int32_t) ceill(eggs_per_f);
+        assert(eggs_per_female >= 0);
+
+        if (eggs_per_female == 0) return;
+
+
+        // how many females will leave the cell
+        // wandering_females: the number of females that will leave the cell and do a random walk
+        cats_dt_rates probability_to_leave = (1.0 -  module_conf->probability_to_stay);
+        cats_dt_population wandering_females = poisson_population_capped(ts->rng,
+                                                                         total_females * probability_to_leave,
+                                                                         total_females);
+
+        // stationary_females: the number of females that will not leave the cell, and leave all their eggs here
+        cats_dt_population stationary_females = total_females - wandering_females;
+        assert(stationary_females >= 0);
+        assert(wandering_females >= 0);
+
+
+        const int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+        const bool debug_rw = module_conf->debug_rw;
+
+        int32_t eggs_to_disperse_per_female = (int32_t) ceill(
+                eggs_per_female * (1.0 - module_conf->egg_fraction_source));
+
+        // all the eggs of all the stationary females + the eggs the wandering females leave in the source cell
+        const cats_dt_rates source_cell_eggs = stationary_females * eggs_per_female +
+                                               wandering_females * (eggs_per_female - eggs_to_disperse_per_female);
+
+        data->eggs[row][col] += (float) ceill(source_cell_eggs);
+
+
+        if (eggs_to_disperse_per_female == 0) {
+                log_message(LOG_ERROR, "%s: random walk with no eggs to distribute in cell %d %d", __func__, row, col);
+                return;
+        }
+
+        const cats_dt_rates egg_fraction_step = module_conf->egg_fraction_step;
+
+
+
+        if (local_only) {
+                return;
+        }
+
+        if (debug_rw) {
+                fprintf(module_conf->debug_rw_file, "# total source cell eggs = %d\n", (int) ceill(source_cell_eggs));
+                fprintf(module_conf->debug_rw_file, "# stationary females = %d\n", stationary_females);
+                fprintf(module_conf->debug_rw_file, "# wandering females = %d\n", wandering_females);
+                fprintf(module_conf->debug_rw_file, "# total females = %d\n", wandering_females + stationary_females);
+                fprintf(module_conf->debug_rw_file, "# wandering females source cell eggs = %d\n",
+                        (int) (wandering_females * (eggs_per_female - eggs_to_disperse_per_female)));
+                fprintf(module_conf->debug_rw_file, "# stationary females source cell eggs = %d\n",
+                        (int) (stationary_females * eggs_per_female));
+                fprintf(module_conf->debug_rw_file, "id,row,col,step,steps left,eggs deposited,eggs left\n");
+
+        }
+
+        for (int32_t rw_number = 0; rw_number < wandering_females; rw_number++) {
+                if (debug_rw) {
+
+                        fprintf(module_conf->debug_rw_file, "%d,%d,%d,0,%d,%d,%d\n", rw_number, row, col,
+                                module_conf->animal_dispersal_max_radius,
+                                (int) ceill(eggs_per_female * module_conf->egg_fraction_source),
+                                (int) ceill(eggs_to_disperse_per_female));
+
+                }
+
+
+                single_random_walk(ts, grid, row, col, eggs_to_disperse_per_female, egg_fraction_step, rw_number);
+                ts->rw_debug_random_walks++;
+        }
+        if (debug_rw) {
+                fflush(module_conf->debug_rw_file);
+                fclose(module_conf->debug_rw_file);
+                log_message(LOG_IMPORTANT, "Ending simulation early - first cell with random walks complete");
+                exit_cats(EXIT_SUCCESS);
+        }
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_dispersal.h b/src/modules/butterflies/butterflies_dispersal.h
new file mode 100644
index 0000000000000000000000000000000000000000..d0fe418daccdaa8a03b71d48406dec781206f9ad
--- /dev/null
+++ b/src/modules/butterflies/butterflies_dispersal.h
@@ -0,0 +1,14 @@
+#ifndef CATS_BUTTERFLIES_DISPERSAL_H
+#define CATS_BUTTERFLIES_DISPERSAL_H
+
+#include "threading/threading.h"
+#include "cats/configuration/configuration.h"
+
+void
+butterflies_cell_dispersal(struct cats_grid *grid, struct cats_thread_info *ts,
+                           cats_dt_coord row, cats_dt_coord col,
+                           bool check_exclusion,
+                           bool local_only);
+
+void bf_initial_population_to_eggs(struct cats_grid *grid, struct cats_thread_info *ts);
+#endif //CATS_BUTTERFLIES_DISPERSAL_H
diff --git a/src/modules/butterflies/butterflies_generations.c b/src/modules/butterflies/butterflies_generations.c
new file mode 100644
index 0000000000000000000000000000000000000000..3e0ab6617f8f0d0d4efe25115d927ac16138080c
--- /dev/null
+++ b/src/modules/butterflies/butterflies_generations.c
@@ -0,0 +1,73 @@
+#include "butterflies_populations.h"
+#include "inline_overlays.h"
+#include "butterflies_main.h"
+#include "modules/module_header.h"
+#include "actions/cats_actions.h"
+#include "butterflies_generations.h"
+#include "inline.h"
+
+// only run at the start of the year
+void bf_area_generation_update(struct cats_grid *grid, struct cats_thread_info *ts)
+{
+        struct cats_configuration *conf = ts->conf;
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+        struct cats_vital_rate *rate = &module_conf->butterfly_generations;
+
+        const cats_dt_coord start_row = ts->area.start_row;
+        const cats_dt_coord end_row = ts->area.end_row;
+        const cats_dt_coord start_col = ts->area.start_col;
+        const cats_dt_coord end_col = ts->area.end_col;
+
+        struct statistics *stats = &ts->stats[grid->id];
+        const cats_dt_rates ot = grid->param.OT;
+
+        const int64_t egg_cells_id = module_conf->stat_ids[BF_CELLS_WITH_EGGS];
+        const int64_t egg_cells_removed_id = module_conf->stat_ids[BF_CELLS_WITH_EGGS];
+        int64_t cells_with_eggs = 0;
+        int64_t cells_with_eggs_removed = 0;
+
+        for (cats_dt_coord row = start_row; row < end_row; row++) {
+                for (cats_dt_coord col = start_col; col < end_col; col++) {
+
+                        if (get_suitability(grid, row, col) < ot)
+                        {
+                                if (data->eggs[row][col] > 0) cells_with_eggs_removed += 1;
+                                data->eggs[row][col] = 0.0f;
+                                data->generations[row][col] = 0.0f;
+                                set_population_ignore_cc(grid, row, col, 0);
+
+                                continue;
+                        }
+
+                        if (cell_excluded_by_overlay(conf, row, col)) {
+                                if (data->eggs[row][col] > 0) cells_with_eggs_removed += 1;
+                                data->eggs[row][col] = 0.0f;
+                                data->generations[row][col] = 0.0f;
+                                set_population_ignore_cc(grid, row, col, 0);
+                                continue;
+                        }
+
+                        if ( ! (data->info_layer[row][col] & BF_CELL_VALID_DISPERSAL_TARGET)){
+                                if (data->eggs[row][col] > 0) cells_with_eggs_removed += 1;
+                                data->eggs[row][col] = 0.0f;
+                                data->generations[row][col] = 0.0f;
+                                set_population_ignore_cc(grid, row, col, 0);
+                                continue;
+                        }
+
+                        cats_dt_rates gen = calculate_rate(rate, 0, conf->param, grid, row, col, NULL);
+
+                        data->generations[row][col] = (float) gen;
+                        if (data->eggs[row][col] > 0) cells_with_eggs += 1;
+
+                }
+        }
+
+        stats->custom_stats[egg_cells_id] += cells_with_eggs;
+        stats->custom_stats[egg_cells_removed_id] += cells_with_eggs_removed;
+
+
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_generations.h b/src/modules/butterflies/butterflies_generations.h
new file mode 100644
index 0000000000000000000000000000000000000000..5df061be22cd4cf788a65ed00eab4d09ba65f98a
--- /dev/null
+++ b/src/modules/butterflies/butterflies_generations.h
@@ -0,0 +1,9 @@
+#ifndef CATS_BUTTERFLIES_GENERATIONS_H
+#define CATS_BUTTERFLIES_GENERATIONS_H
+
+#include "data/cats_grid.h"
+#include "threading/threading.h"
+
+void bf_area_generation_update(struct cats_grid *grid, struct cats_thread_info *ts);
+
+#endif //CATS_BUTTERFLIES_GENERATIONS_H
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_initial_population.c b/src/modules/butterflies/butterflies_initial_population.c
new file mode 100644
index 0000000000000000000000000000000000000000..f4fc811d292b12b08cf0ad73a594ee11e36c3319
--- /dev/null
+++ b/src/modules/butterflies/butterflies_initial_population.c
@@ -0,0 +1,112 @@
+
+#include "lambda/leslie_matrix.h"
+#include "inline.h"
+
+#include "butterflies_dispersal.h"
+#include "butterflies_generations.h"
+#include "populations/population.h"
+#include "paths/paths.h"
+
+#include "butterflies_populations.h"
+
+#include "butterflies_main.h"
+#include "butterflies_actions.h"
+#include "modules/module_header.h"
+#include "actions/cats_actions.h"
+#include "butterflies_initial_population.h"
+
+
+void bf_initial_population_adjustment(struct cats_configuration *conf, struct cats_grid *grid)
+{
+        int64_t init_populated_cells = count_populated_cells(grid);
+        int64_t invalid_resources = butterflies_prune_invalid_cells(grid);
+        log_message(LOG_IMPORTANT, "Loaded initial populations: %ld cells occupied before adjustment",
+                    init_populated_cells);
+        if (grid->param.initial_population.set_to_cc == true) {
+
+//#define DEBUG_INITIAL_POPULATIONS 1
+#ifdef DEBUG_INITIAL_POPULATIONS
+                const int32_t max_i = 10;
+                struct cats_vital_rate *cc_rate = &grid->param.carrying_capacity;
+                cats_dt_rates OT =  grid->param.OT;
+                for (int32_t i = 0; i < max_i; i++) {
+
+                        cats_dt_rates suit = OT + i * (1.0 - OT)/max_i;
+                        cats_dt_rates cc = cc_rate->func->func(cc_rate, &grid->param, suit, 0, NAN);
+                        log_message(LOG_INFO, "Carrying capacity for suitability %Lf: %Lf", suit, cc);
+
+                }
+                const cats_dt_coord rows = grid->dimension.rows;
+                const cats_dt_coord cols = grid->dimension.cols;
+                int64_t start = 0;
+                int64_t multi_excluded = 0;
+
+                for (cats_dt_coord row = 0; row < rows; row++) {
+                        for (cats_dt_coord col = 0; col < cols; col++) {
+
+                                if (get_adult_population(grid, row, col) == 0) continue;
+                                start += 1;
+                                cats_dt_rates multiplier = 1.0;
+                                if (conf->overlays.overlay[OL_HABITAT_TYPE_CC].enabled) {
+                                        multiplier *= conf->overlays.habitat_cc->data[row][col];
+                                }
+                                cats_dt_rates suit = get_suitability(grid, row, col);
+                                cats_dt_rates cc_raw = cc_rate->func->func(cc_rate, &grid->param, suit, 0, NAN);
+                                if (multiplier == 0) {
+                                    multi_excluded += 1;
+                                }
+                                cats_dt_population cc = get_carrying_capacity(grid, row, col);
+
+                                printf("DEBUG::row %d col %d, suit %Lf, cc multi %Lf, cc raw %Lf, cc %d\n", row, col, suit, multiplier, cc_raw, cc);
+                        }
+                }
+
+
+
+#endif
+
+                increase_initial_population_to_cc(grid, conf);
+                int64_t populated_cells_after_cc = count_populated_cells(grid);
+                log_message(LOG_IMPORTANT, "\t%ld cells occupied after setting population sizes to carrying capacity",
+                            populated_cells_after_cc);
+        }
+
+
+        if (grid->param.initial_population.suitability_threshold > 0.0) {
+                prune_initial_population_under_threshold(conf, grid);
+        }
+
+        int64_t after_populated_cells = count_populated_cells(grid);
+
+        log_message(LOG_IMPORTANT, "Pruned initial populations from %ld cells to %ld, (%ld wrong habitat)",
+                    init_populated_cells, after_populated_cells, invalid_resources);
+
+
+        cats_dt_population rows = grid->dimension.rows;
+        cats_dt_population cols = grid->dimension.cols;
+
+        threaded_action(&bf_initial_population_to_eggs, grid, conf, TS_DISPERSAL);
+
+#ifdef BF_DEBUG
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+#endif
+
+
+        for (cats_dt_coord row = 0; row < rows; row++) {
+                for (cats_dt_coord col = 0; col < cols; col++) {
+#ifdef BF_DEBUG
+                        if (get_adult_population(grid, row, col) || data->eggs[row][col]) {
+                                printf(""
+                                       "BFDBG::%s::initial adults -> eggs::%d,%d,%d,%f\n",
+                                       __func__, row, col, get_adult_population(grid, row, col),
+                                       data->eggs[row][col]);
+                        }
+#endif
+                        set_population_ignore_cc(grid, row, col, 0);
+                }
+        }
+
+
+        grid->param.initial_population.adjusted = true;
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_initial_population.h b/src/modules/butterflies/butterflies_initial_population.h
new file mode 100644
index 0000000000000000000000000000000000000000..718f3cf48fa1380bc16b757767ea4107ceb4b04f
--- /dev/null
+++ b/src/modules/butterflies/butterflies_initial_population.h
@@ -0,0 +1,5 @@
+#ifndef CATS_BUTTERFLIES_INITIAL_POPULATION_H
+#define CATS_BUTTERFLIES_INITIAL_POPULATION_H
+#include "data/cats_grid.h"
+void bf_initial_population_adjustment(struct cats_configuration *conf, struct cats_grid *grid);
+#endif //CATS_BUTTERFLIES_INITIAL_POPULATION_H
diff --git a/src/modules/butterflies/butterflies_inline.h b/src/modules/butterflies/butterflies_inline.h
new file mode 100644
index 0000000000000000000000000000000000000000..2551ab7c3d6894228886493398ed61fdeb0c9482
--- /dev/null
+++ b/src/modules/butterflies/butterflies_inline.h
@@ -0,0 +1,19 @@
+#ifndef CATS_BUTTERFLIES_INLINE_H
+#define CATS_BUTTERFLIES_INLINE_H
+
+#include <math.h>
+#include <stdbool.h>
+
+#include "butterflies_main.h"
+#include "modules/module_header.h"
+
+
+static inline bool bf_cell_excluded_by_generation(const struct cats_grid *grid, cats_dt_coord row, cats_dt_coord col)
+{
+        const int module_id = CATS_MODULE_ID;
+        const struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+        if (data->generation_current == 0.0 || data->generation_current > (int32_t) ceilf(data->generations[row][col])) return true;
+        return false;
+}
+
+#endif //CATS_BUTTERFLIES_INLINE_H
diff --git a/src/modules/butterflies/butterflies_main.c b/src/modules/butterflies/butterflies_main.c
new file mode 100644
index 0000000000000000000000000000000000000000..bd1ee7b29daa0262dc949bb5361af95ea7417ae0
--- /dev/null
+++ b/src/modules/butterflies/butterflies_main.c
@@ -0,0 +1,194 @@
+#include <math.h>
+#include "modules/module_header.h"
+#include "butterflies_main.h"
+#include "configuration/load_configuration_species_params.h"
+#include "module.h"
+#include "actions/cats_actions.h"
+#include "butterflies_actions.h"
+#include "butterflies_vital_rates.h"
+#include "cats_ini/cats_ini.h"
+#include "butterflies_actions_setup.h"
+#include "paths/output_paths.h"
+#include "paths/directory_helper.h"
+#include "butterflies_populations.h"
+#include "butterflies_paths.h"
+struct cats_global global;
+struct cats_debug_options cats_debug;
+
+#include "lambda/leslie_matrix.h"
+#include "actions/setup_actions.h"
+#include "butterflies_scale.h"
+
+
+cats_dt_rates bf_expected_local_eggs_per_female(struct cats_configuration *conf, struct cats_grid *grid, cats_dt_coord row, cats_dt_coord col)
+{
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        cats_dt_rates stationary = module_conf->probability_to_stay;
+        cats_dt_rates mobile = 1.0 - stationary;
+        cats_dt_rates egg_fraction_source = module_conf->egg_fraction_source;
+        cats_dt_rates eggs_per_female = calculate_rate(&module_conf->eggs_per_female, NAN, &grid->param, grid, row, col, NULL);
+        cats_dt_rates local_eggs =  (stationary + mobile * egg_fraction_source ) * eggs_per_female;
+        return local_eggs;
+}
+
+
+
+void *butterfly_grid_init(__attribute__((unused)) struct cats_configuration *conf, struct cats_grid *grid,
+                          __attribute__((unused)) void *ignored)
+{
+        log_message(LOG_INFO, "%s: %s: grid init of grid %d with %d rows and %d cols",
+                    module_name, __func__, grid->id, grid->dimension.cols, grid->dimension.rows);
+
+
+        struct grid_data_butterflies *data = malloc_or_die(sizeof(struct grid_data_butterflies));
+        log_message(LOG_INFO, "allocating data for generations");
+        data->generations = new_raw_2d_array_from_dimension(grid->dimension, sizeof(float));
+        data->info_layer = new_raw_2d_array_from_dimension(grid->dimension, sizeof(int32_t));
+        log_message(LOG_INFO, "done allocating data for generations");
+        data->generation_current = 0;
+
+        //struct conf_data_butterflies *conf_data = CATS_MODULE_DATA;
+        data->eggs = new_raw_2d_array_from_dimension(grid->dimension, sizeof(float));
+
+        if (grid->param.parametrization != PARAM_HYBRID) {
+                log_message(LOG_ERROR, "%s only works with hybrid parametrisation mode", module_name);
+                exit_cats(EXIT_FAILURE);
+
+        }
+
+        return data;
+}
+
+
+void *
+butterfly_grid_cleanup(__attribute__((unused)) struct cats_configuration *conf, struct cats_grid *grid, void *data)
+{
+        log_message(LOG_INFO, "%s: grid cleanup", module_name);
+        assert(grid != NULL);
+        struct grid_data_butterflies *grid_data = data;
+        free_grid(&grid_data->generations, grid->dimension.rows);
+        free(grid_data->generations);
+        grid_data->generations = NULL;
+        free_grid(&grid_data->info_layer, grid->dimension.rows);
+        free(grid_data->info_layer);
+        grid_data->info_layer = NULL;
+        grid_data->generation_current = -1;
+        return NULL;
+}
+
+
+void load_butterflies_species_params(struct cats_configuration *conf, struct cats_ini *ini, const char *section_name,
+                                     struct cats_species_param *param)
+{
+        struct conf_data_butterflies *data = CATS_MODULE_DATA;
+
+        load_conf_vital_rate(&data->eggs_per_female, conf, ini, section_name, param);
+        load_conf_vital_rate(&data->butterfly_generations, conf, ini, section_name, param);
+        load_conf_vital_rate(&data->reproduction_rate, conf, ini, section_name, param);
+
+
+        //load_conf_vital_rate(&data->butterfly_egg_to_adult_survival, conf, ini, section_name, param);
+
+        load_conf_value(true, ini, section_name, "butterflies random walk steps maximum",
+                        &data->animal_dispersal_max_radius);
+        load_conf_value(true, ini, section_name, "butterflies egg fraction source", &data->egg_fraction_source);
+        bool l = load_conf_value(false, ini, section_name, "butterflies egg fraction step", &data->egg_fraction_step);
+
+        if (!l) {
+                data->egg_fraction_step = 0.5;
+                log_message(LOG_INFO, "using default value for butterflies egg fraction step: %Lf\n",
+                            data->egg_fraction_step);
+        }
+
+        l = load_conf_value(false, ini, section_name, "butterflies female fraction", &data->female_fraction);
+
+        if (!l) {
+                data->female_fraction = 0.5;
+                log_message(LOG_INFO, "using default value for butterflies female fraction: %Lf\n",
+                            data->female_fraction);
+        }
+        if (data->female_fraction <= 0.0 || data->female_fraction >= 1.0) {
+                log_message(LOG_ERROR, "butterflies female fraction must be in (0, 1), is %Lf", data->female_fraction);
+                exit_cats(EXIT_FAILURE);
+        }
+
+
+        if (data->egg_fraction_step <= 0.0 || data->egg_fraction_step > 1.0) {
+                log_message(LOG_ERROR, "butterflies egg fraction step must be in (0, 1], is %Lf",
+                            data->egg_fraction_step);
+                exit_cats(EXIT_FAILURE);
+        }
+
+        if (data->animal_dispersal_max_radius <= 0) {
+                log_message(LOG_ERROR, "butterflies random walk steps maximum must be > 0, is %d",
+                            data->animal_dispersal_max_radius);
+                exit_cats(EXIT_FAILURE);
+        }
+
+        data->debug_rw = false;
+        data->debug_rw_file = NULL;
+        data->debug_rw_filename = NULL;
+        load_conf_value(true, ini, section_name, "butterflies probability to stay", &data->probability_to_stay);
+        load_conf_value(false, ini, section_name, "butterflies debug random walks", &data->debug_rw);
+
+        if (data->debug_rw) {
+                struct string_array *path = get_output_directory(conf, "debug");
+                check_and_create_directory_if_needed(path);
+                struct string_array *name = new_string_array_init("rw-debug");
+                string_array_add(name, conf->run_name);
+                data->debug_rw_filename = assemble_filename(path, name, "_", "csv");
+                data->debug_rw_file = fopen(data->debug_rw_filename, "w");
+                ENSURE_FILE_OPENED(data->debug_rw_file, data->debug_rw_filename);
+                free_string_array(&path);
+                free_string_array(&name);
+
+                log_message(LOG_IMPORTANT, "Random walk debugging is enabled, reducing number of threads to one");
+                conf->param_max_threads = 1;
+        }
+
+        if (data->probability_to_stay < 0.0 || data->probability_to_stay > 1.0) {
+                log_message(LOG_ERROR, "butterflies probability to stay has to be in range [0, 1]");
+                exit_cats(EXIT_FAILURE);
+        }
+
+        data->generations_max = (int32_t) ceill(data->butterfly_generations.max_rate);
+        data->generations_min = (int32_t) ceill(data->butterfly_generations.min_rate);
+        param->plant_dispersal_max_radius = data->animal_dispersal_max_radius;
+        param->max_adult_cc_fraction = 1.0;
+
+
+        if (!data->actions_added) {
+                bf_register_actions(conf);
+                list_actions_full(conf);
+                log_message(LOG_EMPTY, " ");
+                bf_add_actions(conf);
+                log_message(LOG_EMPTY, " ");
+
+                data->actions_added = true;
+
+
+        }
+
+}
+
+
+int cats_module_init(struct cats_configuration *conf)
+{
+        struct conf_data_butterflies *data = calloc_or_die(1, sizeof(struct conf_data_butterflies));
+        enum cats_module_flags flags = MODULE_ALTERNATE_DEMOGRAPHIC | MODULE_OVERRIDE_ACTIONS;
+        int32_t id = register_module(conf, module_name, data, flags);
+        register_cats_grid_init_function(conf, butterfly_grid_init, butterfly_grid_cleanup);
+        register_load_species_param_config_func(conf, load_butterflies_species_params);
+        register_create_leslie_matrix_func(conf, bf_leslie_matrix);
+        bf_add_vital_rates(conf, data);
+
+        log_message(LOG_INFO, "Hello from '%s' (id: %d)", module_name, id);
+
+
+        for (enum butterfly_stats which = BF_STAT_MIN; which < BF_STAT_MAX; which++) {
+                data->stat_ids[which] = add_custom_stat(&conf->stats_registry, bf_get_stats_field_name(which));
+        }
+
+        bf_add_directories(conf);
+        return id;
+}
diff --git a/src/modules/butterflies/butterflies_main.h b/src/modules/butterflies/butterflies_main.h
new file mode 100644
index 0000000000000000000000000000000000000000..e4d7655c3c9d7f81f3a04233e43708fc8b37a0c6
--- /dev/null
+++ b/src/modules/butterflies/butterflies_main.h
@@ -0,0 +1,60 @@
+#ifndef CATS_BUTTERFLIES_MAIN_H
+#define CATS_BUTTERFLIES_MAIN_H
+
+#include "cats_global.h"
+#include <stdint.h>
+#include "data/cats_datatypes.h"
+#include "vital_rates/vital_rates.h"
+#include "butterflies_stats.h"
+
+
+struct grid_data_butterflies {
+    float **generations;
+    int32_t generation_current;
+    float **eggs;
+    int32_t **info_layer;
+
+
+};
+
+enum butterfly_cell_info {
+    BF_CELL_CLEAR = 0,
+    BF_CELL_EXCLUDED = 1 << 1,
+    BF_CELL_HABITAT_OK = 1 << 2,
+    BF_CELL_RESOURCE_AVAILABLE = 1 << 3,
+    BF_CELL_VALID_DISPERSAL_TARGET = 1 << 4
+
+
+};
+
+#define BF_DEBUG_ROW  4319
+#define BF_DEBUG_COL 10502
+
+struct conf_data_butterflies {
+
+    // fixme -> move to grid data;
+
+    int32_t generations_max;
+    int32_t generations_min;
+    int32_t animal_dispersal_max_radius; ///< maximal flight/dispersal distance
+    cats_dt_rates probability_to_stay;
+    bool debug_rw;
+    FILE *debug_rw_file;
+    char *debug_rw_filename;
+    cats_dt_rates egg_fraction_source;
+    cats_dt_rates egg_fraction_step;
+    struct cats_vital_rate eggs_per_female;
+    struct cats_vital_rate butterfly_egg_to_adult_survival;
+    struct cats_vital_rate butterfly_generations;
+    struct cats_vital_rate reproduction_rate;
+    cats_dt_rates female_fraction;
+
+    bool actions_added;
+
+    int64_t stat_ids[BF_STAT_MAX];
+    FILE *stats_file;
+
+
+};
+
+#endif //CATS_BUTTERFLIES_MAIN_H
diff --git a/src/modules/butterflies/butterflies_overlays.c b/src/modules/butterflies/butterflies_overlays.c
new file mode 100644
index 0000000000000000000000000000000000000000..704b4fb5a10c1c0b60c34bd24a5e5335ad2f190b
--- /dev/null
+++ b/src/modules/butterflies/butterflies_overlays.c
@@ -0,0 +1,103 @@
+#include "paths/paths.h"
+#include "butterflies_populations.h"
+#include "modules/module_header.h"
+#include "actions/cats_actions.h"
+
+#include "butterflies_overlays.h"
+#include "butterflies_main.h"
+#include "inline_overlays.h"
+#include "inline_population.h"
+
+
+enum action_status bf_grid_overlay_update(const struct cats_configuration *conf, struct cats_grid *grid)
+{
+        if (conf->overlays.have_overlays == false) {
+                return ACTION_NOT_RUN;
+        }
+
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+
+        const cats_dt_coord rows = grid->dimension.rows;
+        const cats_dt_coord cols = grid->dimension.cols;
+        int64_t cells_habitat_ok = 0;
+        int64_t cells_excluded = 0;
+        int64_t cells_resource_ok = 0;
+        int64_t cells_habitat_and_resource_ok = 0;
+        int64_t cells_eggs_removed = 0;
+        int64_t cells_adults_removed = 0;
+
+        for (cats_dt_coord row = 0; row < rows; row++) {
+                for (cats_dt_coord col = 0; col < cols; col++) {
+
+                        data->info_layer[row][col] = BF_CELL_CLEAR;
+
+                        if (cell_excluded_by_overlay(conf, row, col)) {
+                                data->info_layer[row][col] |= BF_CELL_EXCLUDED;
+                                if (data->eggs[row][col]) cells_eggs_removed += 1;
+                                if (get_adult_population(grid, row, col)) cells_adults_removed += 1;
+                                data->eggs[row][col] = 0;
+                                set_population_ignore_cc(grid, row, col, 0);
+                                cells_excluded += 1;
+                                continue;
+                        }
+
+
+                        if (conf->overlays.overlay[OL_HABITAT_TYPE_CC].enabled &&
+                            conf->overlays.habitat_cc->data[row][col] > 0) {
+                                data->info_layer[row][col] |= BF_CELL_HABITAT_OK;
+                                cells_habitat_ok += 1;
+                        } else if (!conf->overlays.overlay[OL_HABITAT_TYPE_CC].enabled) {
+                                data->info_layer[row][col] |= BF_CELL_HABITAT_OK;
+                                cells_habitat_ok += 1;
+                        }
+
+                        if (conf->overlays.overlay[OL_RESOURCE].enabled &&
+                            conf->overlays.resources->data[row][col] > 0) {
+                                data->info_layer[row][col] |= BF_CELL_RESOURCE_AVAILABLE;
+                                cells_resource_ok += 1;
+                        } else if (!conf->overlays.overlay[OL_RESOURCE].enabled) {
+                                data->info_layer[row][col] |= BF_CELL_RESOURCE_AVAILABLE;
+                                cells_resource_ok += 1;
+                        }
+
+                        if ((data->info_layer[row][col] & BF_CELL_HABITAT_OK) &&
+                            (data->info_layer[row][col] & BF_CELL_RESOURCE_AVAILABLE)) {
+                                data->info_layer[row][col] |= BF_CELL_VALID_DISPERSAL_TARGET;
+                                cells_habitat_and_resource_ok += 1;
+
+                        } else {
+                                if (data->eggs[row][col]) cells_eggs_removed += 1;
+                                if (get_adult_population(grid, row, col)) cells_adults_removed += 1;
+                                data->eggs[row][col] = 0;
+                                set_population_ignore_cc(grid, row, col, 0);
+                        }
+                }
+        }
+
+        int64_t cells_with_eggs = 0;
+        double total_eggs = 0;
+        for (cats_dt_coord row = 0; row < rows; row++) {
+                for (cats_dt_coord col = 0; col < cols; col++) {
+                        float eggs = data->eggs[row][col];
+                        if (eggs > 0) {
+                                cells_with_eggs += 1;
+                                total_eggs += eggs;
+                        }
+
+                }
+        }
+
+        log_message(LOG_INFO,
+                    "Overlay update: %ld excluded, %ld habitat ok, %ld resource ok, %ld habitat + resource ok",
+                    cells_excluded, cells_habitat_ok, cells_resource_ok, cells_habitat_and_resource_ok);
+        log_message(LOG_INFO, "Overlay update: %ld cells with eggs, total %f eggs, average %f eggs per cell",
+                    cells_with_eggs, total_eggs, total_eggs / (float) cells_with_eggs);
+        log_message(LOG_INFO, "Overlay update: %ld cells with eggs removed", cells_eggs_removed);
+        log_message(LOG_INFO, "Overlay update: %ld cells with adults removed", cells_adults_removed);
+
+
+        return ACTION_RUN;
+}
+
diff --git a/src/modules/butterflies/butterflies_overlays.h b/src/modules/butterflies/butterflies_overlays.h
new file mode 100644
index 0000000000000000000000000000000000000000..4d9b11970130425f3d71cbcc161e4c70646e1c72
--- /dev/null
+++ b/src/modules/butterflies/butterflies_overlays.h
@@ -0,0 +1,7 @@
+#ifndef CATS_BUTTERFLIES_OVERLAYS_H
+#define CATS_BUTTERFLIES_OVERLAYS_H
+#include "configuration/configuration.h"
+
+enum action_status bf_grid_overlay_update(const struct cats_configuration *conf, struct cats_grid *grid);
+
+#endif //CATS_BUTTERFLIES_OVERLAYS_H
diff --git a/src/modules/butterflies/butterflies_paths.c b/src/modules/butterflies/butterflies_paths.c
new file mode 100644
index 0000000000000000000000000000000000000000..29ca2fef3a387c16d5dab0fa6921aa7609f0dfc4
--- /dev/null
+++ b/src/modules/butterflies/butterflies_paths.c
@@ -0,0 +1,110 @@
+#include "butterflies_generations.h"
+#include "paths/paths.h"
+#include "paths/output_paths.h"
+#include "butterflies_populations.h"
+#include "inline_overlays.h"
+#include "butterflies_main.h"
+#include "butterflies_actions.h"
+#include "modules/module_header.h"
+#include "actions/cats_actions.h"
+#include "butterflies_paths.h"
+#include "module.h"
+
+void bf_add_directories(struct cats_configuration *conf)
+{
+        add_module_output_directory(conf, "butterfly-adults");
+        if (conf->output.write_all) {
+                add_module_output_directory(conf,"butterfly-eggs");
+                add_module_output_directory(conf,"butterfly-overlay");
+        }
+
+}
+
+char *bf_population_filename(struct cats_configuration *conf, struct cats_grid *grid)
+{
+        assert(grid != NULL);
+        assert(conf != NULL);
+        assert(conf->grid_count == 1);
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        int32_t max_generation = module_conf->generations_max;
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+
+        struct string_array *path = get_output_directory(conf, "butterfly-adults"); // FIXME MAKE DIRECTORY
+
+        char *extension = get_extension(conf, "adults");
+        struct string_array *name = standard_output_file_name(conf, NULL, NULL, NULL);
+        string_array_add_int(name, max_generation - data->generation_current, "g%03d");
+
+        char *filename = assemble_filename(path, name, "_", extension);
+
+        free_string_array(&path);
+        free_string_array(&name);
+        free(extension);
+
+        return filename;
+}
+
+char *bf_population_eggs_filename(struct cats_configuration *conf, struct cats_grid *grid)
+{
+        assert(grid != NULL);
+        assert(conf != NULL);
+        assert(conf->grid_count == 1);
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        int32_t max_generation = module_conf->generations_max;
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+
+        struct string_array *path = get_output_directory(conf, "butterfly-eggs"); // FIXME MAKE DIRECTORY
+
+        char *extension = get_extension(conf, "eggs");
+        struct string_array *name = standard_output_file_name(conf, NULL, NULL, NULL);
+        string_array_add_int(name, max_generation - data->generation_current, "g%03d");
+
+        char *filename = assemble_filename(path, name, "_", extension);
+
+        free_string_array(&path);
+        free_string_array(&name);
+        free(extension);
+
+        return filename;
+}
+
+
+char *bf_population_overlay_filename(struct cats_configuration *conf, struct cats_grid *grid)
+{
+        assert(grid != NULL);
+        assert(conf != NULL);
+        assert(conf->grid_count == 1);
+
+
+        struct string_array *path = get_output_directory(conf, "butterfly-overlay"); // FIXME MAKE DIRECTORY
+
+        char *extension = get_extension(conf, "overlay");
+        struct string_array *name = standard_output_file_name(conf, NULL, NULL, NULL);
+        char *filename = assemble_filename(path, name, "_", extension);
+
+        free_string_array(&path);
+        free_string_array(&name);
+        free(extension);
+
+        return filename;
+}
+
+
+char *bf_stats_filename(struct cats_configuration *conf, struct cats_grid *grid)
+{
+        struct string_array *path = get_output_directory(conf, "stats");
+
+        struct string_array *name = new_string_array();
+        string_array_add(name, module_name);
+        string_array_add(name, conf->run_name);
+        string_array_add_int(name, conf->simulation.replicate, "r%03d");
+        char *filename = assemble_filename(path, name, "_", "csv");
+        free_string_array(&path);
+        free_string_array(&name);
+
+        return filename;
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_paths.h b/src/modules/butterflies/butterflies_paths.h
new file mode 100644
index 0000000000000000000000000000000000000000..a109363339cf3b5e428182be01ffd534304da700
--- /dev/null
+++ b/src/modules/butterflies/butterflies_paths.h
@@ -0,0 +1,10 @@
+#ifndef CATS_BUTTERFLIES_PATHS_H
+#define CATS_BUTTERFLIES_PATHS_H
+
+#include "configuration/configuration.h"
+char *bf_population_eggs_filename(struct cats_configuration *conf, struct cats_grid *grid);
+char *bf_population_filename(struct cats_configuration *conf, struct cats_grid *grid);
+char *bf_stats_filename(struct cats_configuration *conf, struct cats_grid *grid);
+char *bf_population_overlay_filename(struct cats_configuration *conf, struct cats_grid *grid);
+void bf_add_directories(struct cats_configuration *conf);
+#endif //CATS_BUTTERFLIES_PATHS_H
diff --git a/src/modules/butterflies/butterflies_populations.c b/src/modules/butterflies/butterflies_populations.c
new file mode 100644
index 0000000000000000000000000000000000000000..07c3c6a1b099f7a7cba247ea7dad1c168e9790f8
--- /dev/null
+++ b/src/modules/butterflies/butterflies_populations.c
@@ -0,0 +1,202 @@
+#include <assert.h>
+#include <math.h>
+
+#include "paths/paths.h"
+#include "paths/output_paths.h"
+#include "actions/cats_actions.h"
+#include "misc/cats_random.h"
+#include "data/cats_grid.h"
+#include "threading/threading-helpers.h"
+#include "modules/module_header.h"
+
+#include "butterflies_populations.h"
+#include "butterflies_main.h"
+#include "butterflies_inline.h"
+#include "inline.h"
+#include "populations/population.h"
+
+int64_t count_populated_cells(const struct cats_grid *grid)
+{
+        int64_t cells = 0;
+        const cats_dt_coord rows = grid->dimension.rows;
+        const cats_dt_coord cols = grid->dimension.cols;
+
+        for (cats_dt_coord row = 0; row < rows; row++) {
+                for (cats_dt_coord col = 0; col < cols; col++) {
+                        if (get_adult_population(grid, row, col)) {
+                                cells += 1;
+                        }
+                }
+        }
+
+        return cells;
+}
+
+
+int64_t butterflies_prune_invalid_cells(struct cats_grid *grid)
+{
+        int64_t invalid_habitat = 0;
+        const int module_id = CATS_MODULE_ID;
+        const struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+        const cats_dt_coord rows = grid->dimension.rows;
+        const cats_dt_coord cols = grid->dimension.cols;
+
+        for (cats_dt_coord row = 0; row < rows; row++) {
+                for (cats_dt_coord col = 0; col < cols; col++) {
+
+                        if (!(data->info_layer[row][col] & BF_CELL_VALID_DISPERSAL_TARGET)) {
+                                if(get_adult_population(grid, row, col)) {
+                                        invalid_habitat += 1;
+                                }
+                                set_population_ignore_cc(grid, row, col, 0);
+                        }
+                }
+        }
+
+        return invalid_habitat;
+}
+
+
+cats_dt_rates
+bf_egg_to_adult_survival_rate(cats_dt_rates reproduction_rate, cats_dt_rates eggs)
+{
+        return reproduction_rate / eggs;
+}
+
+float get_fractional_generation(struct cats_grid *grid, cats_dt_coord row, cats_dt_coord col)
+{
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        const int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+        float this_generation_fraction = 1.0f;
+
+        float local_generation = data->generations[row][col];
+        int32_t global_max_generation = module_conf->generations_max;
+        int32_t local_max_generation = (int32_t) ceilf(local_generation);
+        int32_t global_current_generation = data->generation_current;
+        float current_generation_float = (float) data->generation_current;
+
+        if (global_current_generation == global_max_generation) {
+                this_generation_fraction = 1.0f;
+        } else if (current_generation_float == local_generation
+                   || data->generation_current == module_conf->generations_min) {
+                this_generation_fraction = 1.0f;
+        } else if (data->generation_current == local_max_generation) {
+                this_generation_fraction = (float) local_max_generation - local_generation;
+        }
+
+        assert(this_generation_fraction > 0);
+        assert(this_generation_fraction <= 1.0);
+
+        return this_generation_fraction;
+}
+
+
+void bf_cell_maturation(struct cats_grid *grid, struct cats_thread_info *ts, cats_dt_coord row, cats_dt_coord col,
+                        bool check_exclusion)
+{
+
+        const struct cats_configuration *conf = ts->conf;
+        assert (conf->grid_count == 1);
+        if (check_exclusion) {
+                if (bf_cell_excluded_by_generation(grid, row, col)) return;
+        }
+
+
+        const int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+        if (data->eggs[row][col] == 0) return;
+        if (data->eggs[row][col] < 0) {
+                log_message(LOG_ERROR, "Number of eggs < 0: row %d col %d: %f", row, col, data->eggs[row][col]);
+                exit(EXIT_FAILURE);
+        }
+
+
+        // The number of generations per cell is usually not an integer value:
+        // the non-integer part of the number of generations is used in the generation which is
+        // one greater than the integer part of the number of generations.
+        // i.e. if the number of generations is 2.4, 40% of all eggs in the cell
+        // will be considered as if they belonged to generation 3
+        // the minimum number of generations is 1 if the cell is climatically viable (and the host species is present)
+        // if the number of generations is 0, the cell will be ignored
+        //
+        // note that the generations are processed backwards
+        // i.e. if the maximum number of generations is 5, individual cells will have 1 ... 5 generations
+        // first the all cells which have 5 (> 4) generations are modelled
+        // then all the cells with have at least 4 (> 3) generations are modelled
+        // ...
+        // at last all cells with have at least 1 generation are modelled
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+
+        float this_generation_fraction = get_fractional_generation(grid, row, col);
+
+
+
+        //cats_dt_population max_cc = (cats_dt_population) grid->param.carrying_capacity.max_rate;
+        const float eggs = this_generation_fraction * data->eggs[row][col];
+#ifdef BF_DEBUG
+        printf("BFDBG::%s::row %d, col %d: local gen %f gen fraction %f, eggs %f, this gen eggs %f\n",
+               __func__, row, col, local_generation, this_generation_fraction, data->eggs[row][col], eggs);
+#endif
+        if (eggs == 0) return;
+
+        if (eggs > data->eggs[row][col]) {
+                log_message(LOG_ERROR, "Removing more eggs than present: %d %d: %f/%f", row, col, data->eggs[row][col],
+                            eggs);
+                exit_cats(EXIT_FAILURE);
+        }
+
+        data->eggs[row][col] -= eggs;
+        assert(data->eggs[row][col] >= 0);
+
+        // not capped, we can have more adults than CC
+
+        cats_dt_environment suit = get_suitability(grid, row, col);
+        cats_dt_rates reproduction_rate = calculate_rate(&module_conf->reproduction_rate, NAN, &grid->param,
+                                                         grid, row, col, NULL);
+        if (suit < grid->param.OT && reproduction_rate > 0) {
+                log_message(LOG_ERROR, "Suitability %f under OT %Lf, but adults per female = %Lf", suit, grid->param.OT,
+                            reproduction_rate);
+                exit_cats(EXIT_FAILURE);
+        }
+
+        //printf("row %d col %d: suitability: %f, eggs: %f, reproduction rate %Lf\n", row, col, suit, eggs, reproduction_rate);
+
+        if (reproduction_rate == 0) {
+                data->eggs[row][col] = 0;
+                set_population_ignore_cc(grid, row, col, 0);
+                return;
+        }
+
+        cats_dt_rates expected_eggs = bf_expected_local_eggs_per_female(ts->conf, grid, row, col);
+        cats_dt_rates survival = bf_egg_to_adult_survival_rate(reproduction_rate, expected_eggs)/module_conf->female_fraction;
+        cats_dt_population adults = poisson(ts->rng, eggs * survival);
+        assert(adults >= 0);
+        set_population(grid, row, col, adults);
+#ifdef BF_DEBUG
+        adults = get_adult_population(grid, row, col);
+        printf("BFDBG::%s::row %d, col %d: year %d, suitability %f, OT %Lf\n", __func__, row, col, conf->time.year_current, suit, grid->param.OT);
+        printf("BFDBG::%s::row %d, col %d: year %d, eggs %f, survival rate %Lf, reproduction rate %Lf, adults %d\n",
+               __func__, row, col, conf->time.year_current, eggs, survival, reproduction_rate, adults);
+        printf("XXXX,%d,%f,%d\n", conf->time.year_current, current_generation_float,adults);
+#endif
+
+
+}
+
+
+void bf_area_kill_adults(struct cats_grid *grid, struct cats_thread_info *ts)
+{
+        const cats_dt_coord start_row = ts->area.start_row;
+        const cats_dt_coord end_row = ts->area.end_row;
+        const cats_dt_coord start_col = ts->area.start_col;
+        const cats_dt_coord end_col = ts->area.end_col;
+
+        for (cats_dt_coord row = start_row; row < end_row; row++) {
+                for (cats_dt_coord col = start_col; col < end_col; col++) {
+                        set_population_ignore_cc(grid, row, col, 0);
+                }
+        }
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_populations.h b/src/modules/butterflies/butterflies_populations.h
new file mode 100644
index 0000000000000000000000000000000000000000..894fce21b4a0c8315181dc58ce50c016bf901cdb
--- /dev/null
+++ b/src/modules/butterflies/butterflies_populations.h
@@ -0,0 +1,14 @@
+//
+// Created by gattringera on 21/11/22.
+//
+
+#ifndef CATS_BUTTERFLIES_POPULATIONS_H
+#define CATS_BUTTERFLIES_POPULATIONS_H
+#include "data/cats_grid.h"
+int64_t count_populated_cells(const struct cats_grid *grid);
+void bf_cell_maturation(struct cats_grid *grid, struct cats_thread_info *ts, cats_dt_coord row, cats_dt_coord col, bool check_exclusion);
+void bf_area_kill_adults(struct cats_grid *grid, struct cats_thread_info *ts);
+cats_dt_rates bf_egg_to_adult_survival_rate(cats_dt_rates reproduction_rate, cats_dt_rates eggs);
+cats_dt_rates bf_expected_local_eggs_per_female(struct cats_configuration *conf, struct cats_grid *grid, cats_dt_coord row, cats_dt_coord col);
+int64_t butterflies_prune_invalid_cells(struct cats_grid *grid);
+#endif //CATS_BUTTERFLIES_POPULATIONS_H
diff --git a/src/modules/butterflies/butterflies_scale.c b/src/modules/butterflies/butterflies_scale.c
new file mode 100644
index 0000000000000000000000000000000000000000..53627e85d7c4126c8695708abea9bf05f83a4793
--- /dev/null
+++ b/src/modules/butterflies/butterflies_scale.c
@@ -0,0 +1,37 @@
+//
+// Created by andreas on 03/07/23.
+//
+#include "modules/module_header.h"
+#include "butterflies_main.h"
+#include "butterflies_scale.h"
+#include "butterflies_populations.h"
+
+
+double *bf_leslie_matrix(struct cats_configuration *conf, struct lambda_parameters *l_param,
+                         bool silent, int32_t *N_out){
+        bool print_rate = !silent;
+
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        cats_dt_rates eggs_per_female = calculate_rate_for_matrix(&module_conf->eggs_per_female, l_param, print_rate);
+        cats_dt_rates reproduction_rate = calculate_rate_for_matrix(&module_conf->reproduction_rate, l_param, print_rate);
+        cats_dt_rates stationary = module_conf->probability_to_stay;
+        cats_dt_rates mobile = 1.0 - stationary;
+        cats_dt_rates egg_fraction_source = module_conf->egg_fraction_source;
+
+        cats_dt_rates local_eggs =  (stationary + mobile * egg_fraction_source ) * eggs_per_female;
+
+        // female -> female
+        // to achieve the target reproduction rate, the number of eggs per female laid in the cell
+        // that survive and become adult has to be the reproduction rate divided by the female fraction divided by the number of eggs
+
+        cats_dt_rates eggs_to_adults_rate = bf_egg_to_adult_survival_rate(reproduction_rate, local_eggs) ;
+        cats_dt_rates result =  local_eggs * eggs_to_adults_rate;
+
+        printf("scale %Lf: eggs %Lf, eggs to adults %Lf\n", conf->param->scale_factor, eggs_per_female, eggs_to_adults_rate);
+        double *matrix = calloc_or_die(1, sizeof(double));
+
+        *matrix = (double) result;
+        *N_out = 1;
+        return matrix;
+
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_scale.h b/src/modules/butterflies/butterflies_scale.h
new file mode 100644
index 0000000000000000000000000000000000000000..3dbcd1d911a04e928e5159901818982804dfc5da
--- /dev/null
+++ b/src/modules/butterflies/butterflies_scale.h
@@ -0,0 +1,16 @@
+//
+// Created by andreas on 03/07/23.
+//
+
+#ifndef CATS_BUTTERFLIES_SCALE_H
+#define CATS_BUTTERFLIES_SCALE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "configuration/configuration.h"
+#include "lambda/leslie_matrix.h"
+
+double *bf_leslie_matrix(struct cats_configuration *conf, struct lambda_parameters *l_param,
+                         bool silent, int32_t *N_out);
+
+#endif //CATS_BUTTERFLIES_SCALE_H
diff --git a/src/modules/butterflies/butterflies_stats.c b/src/modules/butterflies/butterflies_stats.c
new file mode 100644
index 0000000000000000000000000000000000000000..20925e9467b81c86abd65d121e3b86430ea1150f
--- /dev/null
+++ b/src/modules/butterflies/butterflies_stats.c
@@ -0,0 +1,185 @@
+
+#include "butterflies_stats.h"
+#include "logging.h"
+#include "cats_global.h"
+#include "data/cats_grid.h"
+#include "modules/module_header.h"
+#include "butterflies_main.h"
+#include "inline_overlays.h"
+#include "inline_population.h"
+#include "inline.h"
+#include "butterflies_paths.h"
+#include "temporal/phase_names.h"
+#include "temporal/years.h"
+
+const char *bf_get_stats_field_name(enum butterfly_stats which)
+{
+        switch (which) {
+                case BF_STAT_POPULATED_FIT:
+                        return "populated_fit";
+                case BF_STAT_POPULATED_UNFIT:
+                        return "populated_unfit";
+                case BF_STAT_UNPOPULATED_FIT:
+                        return "unpopulated_fit";
+                case BF_STAT_UNPOPULATED_UNFIT:
+                        return "unpopulated_unfit";
+                case BF_STAT_EXCLUDED:
+                        return "excluded";
+                case BF_STAT_GENERATION_ACTIVE:
+                        return "generation_active";
+                case BF_POPULATED_AT_DISPERSAL:
+                        return "populated_at_dispersal";
+
+                case BF_RANDOM_WALK_COUNT:
+                        return "random_walk_count";
+
+                case BF_RANDOM_WALK_STEP_COUNT:
+                        return "random_walk_step_count";
+                case BF_OUTPUT_STAT_MAX:
+                        return "<guard value>";
+
+                case BF_STAT_MAX:
+                        break;
+                case BF_CELLS_WITH_EGGS:
+                        return "cells with eggs";
+                case BF_CELLS_WITH_EGGS_REMOVED:
+                        return "removed cells with eggs";
+
+        }
+
+        log_message(LOG_ERROR, "unknown butterfly stats name with id %d", which);
+        exit_cats(EXIT_FAILURE);
+}
+
+struct string_array *bf_assemble_stats(struct cats_configuration *conf, struct cats_grid *grid, bool header)
+{
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        assert(module_conf->stats_file != NULL);
+        const int32_t grid_id = grid->id;
+        int module_id = CATS_MODULE_ID;
+        struct grid_data_butterflies *data = grid->grid_modules[module_id].module_data;
+
+
+        struct string_array *x = new_string_array();
+
+        if (header) {
+
+                string_array_add(x, "phase");
+                string_array_add(x, "year");
+                string_array_add(x, "run");
+                string_array_add(x, "replicate");
+                string_array_add(x, "species");
+                string_array_add(x, "id");
+                string_array_add(x, "generation");
+
+                for (enum butterfly_stats which = BF_STAT_MIN; which < BF_OUTPUT_STAT_MAX; which++) {
+                        const char *name = bf_get_stats_field_name(which);
+                        string_array_add(x, name);
+                }
+        } else {
+                string_array_add(x, get_phase_shortname(conf->time.phase));
+                string_array_add_int64(x, get_phase_year_abs(conf), NULL);
+                string_array_add(x, conf->run_name);
+                string_array_add_int64(x, conf->simulation.replicate, NULL);
+                string_array_add(x, conf->param[grid_id].species_name);
+                string_array_add_int64(x, grid_id, NULL);
+                string_array_add_int(x, data->generation_current, NULL);
+
+                for (enum butterfly_stats which = BF_STAT_MIN; which < BF_OUTPUT_STAT_MAX; which++) {
+                        int64_t stat_id = module_conf->stat_ids[which];
+                        string_array_add_int64(x, grid->stats.custom_stats[stat_id], NULL);
+                }
+
+        }
+
+
+        return x;
+
+}
+
+
+void bf_stats_write(struct cats_configuration *conf, struct cats_grid *grid)
+{
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        struct string_array *data = NULL;
+
+        if (module_conf->stats_file == NULL) {
+                char *fn = bf_stats_filename(conf, grid);
+                module_conf->stats_file = fopen(fn, "w");
+                ENSURE_FILE_OPENED(module_conf->stats_file, fn);
+                free(fn);
+                data = bf_assemble_stats(conf, grid, true);
+
+                char *string = string_array_paste(data, ",");
+                fprintf(module_conf->stats_file, "%s\n", string);
+                free(string);
+
+                free_string_array(&data);
+
+        }
+
+        data = bf_assemble_stats(conf, grid, false);
+        char *string = string_array_paste(data, ",");
+        fprintf(module_conf->stats_file, "%s\n", string);
+        free(string);
+        free_string_array(&data);
+}
+
+void bf_area_stats_gather(struct cats_grid *grid, struct cats_thread_info *ts)
+{
+        struct conf_data_butterflies *module_conf = CATS_MODULE_DATA;
+        struct cats_configuration *conf = ts->conf;
+        const int64_t id_excluded = module_conf->stat_ids[BF_STAT_EXCLUDED];
+
+        const int64_t id_pop_fit = module_conf->stat_ids[BF_STAT_POPULATED_FIT];
+        const int64_t id_pop_unfit = module_conf->stat_ids[BF_STAT_POPULATED_UNFIT];
+
+        const int64_t id_unpop_fit = module_conf->stat_ids[BF_STAT_UNPOPULATED_FIT];
+        const int64_t id_unpop_unfit = module_conf->stat_ids[BF_STAT_UNPOPULATED_UNFIT];
+        const int32_t grid_id = grid->id;
+
+        //const cats_dt_rates zt = grid->param.ZT;
+        const cats_dt_rates ot = grid->param.OT;
+        struct statistics *stats = &ts->stats[grid_id];
+
+        const cats_dt_coord start_row = ts->area.start_row;
+        const cats_dt_coord end_row = ts->area.end_row;
+        const cats_dt_coord start_col = ts->area.start_col;
+        const cats_dt_coord end_col = ts->area.end_col;
+
+        for (cats_dt_coord row = start_row; row < end_row; row++) {
+                for (cats_dt_coord col = start_col; col < end_col; col++) {
+                        cats_dt_population pop = get_adult_population(grid, row, col);
+
+                        if (cell_excluded_by_overlay(conf, row, col)) {
+                                stats->custom_stats[id_excluded] += 1;
+                                if (pop > 0) {
+                                        log_message(LOG_ERROR, "%s: population > 0 (%d) in excluded cell %d %d",
+                                                    __func__, pop, row, col);
+                                        exit_cats(EXIT_FAILURE);
+                                }
+                                continue;
+                        }
+
+                        cats_dt_environment suit = get_suitability(grid, row, col);
+
+                        if (pop > 0) {
+                                if (suit >= ot) {
+                                        stats->custom_stats[id_pop_fit] += 1;
+
+                                } else {
+                                        stats->custom_stats[id_pop_unfit] += 1;
+                                }
+                        } else {
+                                if (suit >= ot) {
+                                        stats->custom_stats[id_unpop_fit] += 1;
+
+                                } else {
+                                        stats->custom_stats[id_unpop_unfit] += 1;
+                                }
+
+                        }
+                }
+        }
+
+}
\ No newline at end of file
diff --git a/src/modules/butterflies/butterflies_stats.h b/src/modules/butterflies/butterflies_stats.h
new file mode 100644
index 0000000000000000000000000000000000000000..09223497053184c8829e09838263aa2431d1a5f8
--- /dev/null
+++ b/src/modules/butterflies/butterflies_stats.h
@@ -0,0 +1,40 @@
+
+#ifndef CATS_BUTTERFLIES_STATS_H
+#define CATS_BUTTERFLIES_STATS_H
+
+#include <assert.h>
+#include "stats/statistics.h"
+#include "threading/threading.h"
+
+enum butterfly_stats {
+    BF_STAT_MIN = 0,
+    BF_STAT_POPULATED_FIT = 0,
+    BF_STAT_POPULATED_UNFIT,
+    BF_STAT_UNPOPULATED_FIT,
+    BF_STAT_UNPOPULATED_UNFIT,
+    BF_STAT_EXCLUDED,
+    BF_STAT_GENERATION_ACTIVE,
+    BF_OUTPUT_STAT_MAX,
+    BF_POPULATED_AT_DISPERSAL,
+    BF_RANDOM_WALK_COUNT,
+    BF_RANDOM_WALK_STEP_COUNT,
+    BF_CELLS_WITH_EGGS,
+    BF_CELLS_WITH_EGGS_REMOVED,
+    BF_STAT_MAX
+};
+
+const char *bf_get_stats_field_name(enum butterfly_stats which);
+void bf_area_stats_gather(struct cats_grid *grid, struct cats_thread_info *ts);
+void bf_stats_write(struct cats_configuration *conf, struct cats_grid *grid);
+
+static inline void increase_custom_stat(struct statistics *stats, int64_t stat_id, int64_t by)
+{
+        assert(stats != NULL);
+        assert(stat_id >= 0 && stat_id < stats->custom_stat_count);
+        stats->custom_stats[stat_id] += by;
+
+}
+
+
+
+#endif //CATS_BUTTERFLIES_STATS_H
diff --git a/src/modules/butterflies/butterflies_vital_rates.c b/src/modules/butterflies/butterflies_vital_rates.c
new file mode 100644
index 0000000000000000000000000000000000000000..ed0e54a694dff3bdad96e4586799013d5889137c
--- /dev/null
+++ b/src/modules/butterflies/butterflies_vital_rates.c
@@ -0,0 +1,26 @@
+#include "butterflies_vital_rates.h"
+
+void bf_add_vital_rates(struct cats_configuration *conf, struct conf_data_butterflies *data)
+{
+        // number of generations
+        register_module_vital_rate(conf, &data->butterfly_generations,"butterflies generations");
+        set_vital_rate_name(&data->butterfly_generations, "butterflies generations");
+        set_vital_rate_suitability_cutoff_hint(&data->butterfly_generations, HYBRID_SUIT_TS_ZT);
+        set_vital_rate_link_hybrid_function(&data->butterfly_generations, conf, LINK_SUITABILITY_SIGMOID);
+        set_vital_density(&data->butterfly_generations, NO_DENSITY_DEP);
+        set_vital_rate_minimum(&data->butterfly_generations, 1.0);
+
+
+
+        // adult to eggs
+        register_module_vital_rate(conf, &data->eggs_per_female, "butterflies eggs per female");
+        set_vital_rate_name(&data->eggs_per_female, "butterflies eggs per female");
+        set_vital_rate_suitability_cutoff_hint(&data->eggs_per_female, HYBRID_SUIT_TS_ZT);
+        set_vital_rate_link_hybrid_function(&data->eggs_per_female, conf, LINK_CONSTANT);
+
+        // eggs to adults
+        register_module_vital_rate(conf, &data->reproduction_rate, "butterflies reproduction rate");
+        set_vital_rate_name(&data->reproduction_rate, "butterflies reproduction rate");
+        set_vital_rate_link_hybrid_function(&data->reproduction_rate, conf, LINK_SUITABILITY_SIGMOID);
+        set_vital_rate_suitability_cutoff_hint(&data->reproduction_rate, HYBRID_SUIT_TS_ZT);
+}
diff --git a/src/modules/butterflies/butterflies_vital_rates.h b/src/modules/butterflies/butterflies_vital_rates.h
new file mode 100644
index 0000000000000000000000000000000000000000..1a1d097bc9b7e5b57376102937c6a8cc34f3d99c
--- /dev/null
+++ b/src/modules/butterflies/butterflies_vital_rates.h
@@ -0,0 +1,10 @@
+
+#ifndef CATS_BUTTERFLIES_VITAL_RATES_H
+#define CATS_BUTTERFLIES_VITAL_RATES_H
+#include "configuration/configuration.h"
+#include "butterflies_main.h"
+#include "vital_rates/setup_rates.h"
+#include "modules/module_header.h"
+
+void bf_add_vital_rates(struct cats_configuration *conf, struct conf_data_butterflies *data) ;
+#endif //CATS_BUTTERFLIES_VITAL_RATES_H
diff --git a/src/modules/butterflies/module.h b/src/modules/butterflies/module.h
new file mode 100644
index 0000000000000000000000000000000000000000..6da8133282863e8ada0fc405c2893b82d98540de
--- /dev/null
+++ b/src/modules/butterflies/module.h
@@ -0,0 +1,6 @@
+#ifndef CATS_MODULE_H
+#define CATS_MODULE_H
+
+static const char *module_name = "cats-butterflies";
+
+#endif //CATS_MODULE_H
diff --git a/src/modules/cats_test_module/register_module.c b/src/modules/cats_test_module/register_module.c
index 7e12973861abfb7036f210e2db19e50bbd2aab72..c97616d35e85e56ecbdeb33dfc15df8e1af0b889 100644
--- a/src/modules/cats_test_module/register_module.c
+++ b/src/modules/cats_test_module/register_module.c
@@ -59,7 +59,7 @@ void cats_module_init(struct cats_configuration *conf)
         enum cats_module_flags flags = MODULE_NO_FLAGS;
         int32_t id = register_module(conf, module_name, data, flags);
         register_cats_grid_init_function(conf, grid_init, grid_cleanup);
-        log_message(LOG_INFO, "Hello from %s (id: %d)\n", module_name, id);
+        log_message(LOG_INFO, "Hello from '%s' (id: %d)", module_name, id);
         greeting2();
 
 }