diff --git a/.gitignore b/.gitignore index 9bd72d2..c4a4ac5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ build-Release/ *.workspace *.mk *.tags +/build/ # Hidden source /RangeShiftR/src/.* diff --git a/RandomCheck.h b/Allele.h similarity index 58% rename from RandomCheck.h rename to Allele.h index b4992d2..9acfc97 100644 --- a/RandomCheck.h +++ b/Allele.h @@ -1,40 +1,36 @@ +#ifndef ALLELEH +#define ALLELEH + /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) --------------------------------------------------------------------------*/ - - -//--------------------------------------------------------------------------- - -#ifndef RandomCheckH -#define RandomCheckH - -#include -using namespace std; - -#include "Parameters.h" -#include "RSrandom.h" - -void randomCheck(void); -extern paramSim *paramsSim; -extern RSrandom *pRandom; -//--------------------------------------------------------------------------- +class Allele { + const float value; + const float dominance; +public: + Allele(float alleleValue, float alleleDominance) : value(alleleValue), dominance(alleleDominance) { } + ~Allele() {} + float getAlleleValue() const { return value; }; + float getDominanceCoef() const { return dominance; }; +}; #endif diff --git a/CMakeLists.txt b/CMakeLists.txt index c14a3e3..06869ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,30 +1,35 @@ # Config file for compilation with CMake -if (NOT batchmode) # that is, RScore as a standalone +if(NOT batchmode) # that is, RScore as a standalone cmake_minimum_required(VERSION 3.10) # set the project name and version - project(RScore VERSION 2.1.0) + project(RScore VERSION 3.0.0) # specify the C++ standard - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) - add_executable(RScore Main.cpp Species.cpp Cell.cpp Community.cpp FractalGenerator.cpp Genome.cpp Individual.cpp Landscape.cpp Model.cpp Parameters.cpp Patch.cpp Population.cpp RandomCheck.cpp RSrandom.cpp SubCommunity.cpp Utils.cpp) + + add_executable(RScore Main.cpp Species.cpp Cell.cpp Community.cpp FractalGenerator.cpp GeneticFitnessTrait.cpp Individual.cpp Landscape.cpp Management.cpp Model.cpp NeutralStatsManager.cpp Parameters.cpp Patch.cpp Population.cpp DispersalTrait.cpp RSrandom.cpp NeutralTrait.cpp SpeciesTrait.cpp SubCommunity.cpp Utils.cpp "unit_tests/testIndividual.cpp" "unit_tests/testNeutralStats.cpp" "unit_tests/testPopulation.cpp") + + # turn on unit tests + add_compile_definitions("UNIT_TESTS") else() # that is, RScore compiled as library within RangeShifter_batch - add_library(RScore Species.cpp Cell.cpp Community.cpp FractalGenerator.cpp Genome.cpp Individual.cpp Landscape.cpp Model.cpp Parameters.cpp Patch.cpp Population.cpp RandomCheck.cpp RSrandom.cpp SubCommunity.cpp Utils.cpp) + + add_library(RScore Species.cpp Cell.cpp Community.cpp FractalGenerator.cpp GeneticFitnessTrait.cpp Individual.cpp Landscape.cpp Management.cpp Model.cpp NeutralStatsManager.cpp Parameters.cpp Patch.cpp Population.cpp DispersalTrait.cpp RSrandom.cpp NeutralTrait.cpp SpeciesTrait.cpp SubCommunity.cpp Utils.cpp) endif() -# pass config definitions to compiler -target_compile_definitions(RScore PRIVATE RSWIN64) +if(OMP) + find_package(OpenMP COMPONENTS CXX) + if(OpenMP_CXX_FOUND) + target_link_libraries(RScore PUBLIC OpenMP::OpenMP_CXX) + endif() +endif() # enable LINUX_CLUSTER macro on Linux + macOS if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions("LINUX_CLUSTER") endif() -# Debug Mode by default, unless "release" is passed -if(NOT DEFINED release) - add_compile_definitions(RSDEBUG) -endif() if(NOT batchmode) target_include_directories(RScore PUBLIC "${PROJECT_BINARY_DIR}") -endif() \ No newline at end of file +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1b62ff..fa25488 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ RangeShifter is distributed with three user interfaces, each living in their own All three share the same source code for the core simulation (i.e., the actual model), which lives in this repo (RScore). Each of the interfaces keeps a copy of this core code in a subfolder called RScore, kept in sync with the RScore repo via a git subtree (see Git subtree usage section). -⚠️ If you wish to propose a change to one of the interfaces, please do so in the corresponding repo: [RangeShifter batch mode](https://github.com/RangeShifter/RangeShifter_batch_dev), [RangeShiftR package](https://github.com/RangeShifter/RangeShiftR-package-dev). +⚠ If you wish to propose a change to one of the interfaces, please do so in the corresponding repo: [RangeShifter batch mode](https://github.com/RangeShifter/RangeShifter_batch_dev), [RangeShiftR package](https://github.com/RangeShifter/RangeShiftR-package-dev). *The RangeShifter GUI is currently being rewritten, and is not open source yet. @@ -26,7 +26,7 @@ All three share the same source code for the core simulation (i.e., the actual m #### Maintainers - [@JetteReeg](https://github.com/JetteReeg): RScore repo and lead in R package -- [@TheoPannetier](https://github.com/TheoPannetier): RScore repo and lead in batch mode + Maintainers are responsible for coordinating development efforts and ensuring that RangeShifter keeps building continuously. @@ -61,12 +61,17 @@ In the meantime, we encourage contributors to work in small and frequent commits Any changes regarding the RangeShifter core code should be done in this repository and can afterwards be synced with all interfaces using the git subtree feature (see [Git subtree](https://github.com/RangeShifter/RScore/tree/main?tab=readme-ov-file#usage-git-subtree) section in the README). + #### Bugs -To report a bug, please [open an issue](https://github.com/RangeShifter/RangeShiftR-package-dev/issues/new), using the Bug Report template. -Please do check if a related issue has already open on one of the other interfaces ([here](https://github.com/RangeShifter/RangeShifter_batch-dev/issues) for the batch interface or [here](https://github.com/RangeShifter/RangeShiftR-package-dev) for the R package interface). +To report a bug, please [open an issue](https://github.com/RangeShifter/RangeShiftR-package/issues/new), using the Bug Report template. +Please do check if a related issue has already open on one of the other interfaces ([here](https://github.com/RangeShifter/RangeShifter_batch/issues) for the batch interface or [here](https://github.com/RangeShifter/RangeShiftR-package) for the R package interface). + To propose a bug fix (thank you!!), please create and work on your own branch or fork, from either `main` or `develop` (preferred), and open a pull request when your fix is ready to be merged into the original branch. +As a prerequisite for merging, please ensure that your version passes status check (that is, RScore can still build, and all unit tests are still satisfied). +This can be seen in the Actions panel for every commit and at the bottom of the pull request. + Maintainers will review the pull request, possibly request changes, and eventually integrate the bug fix into RScore, and update the subtrees to bring the fix to all interfaces. #### New features diff --git a/Cell.cpp b/Cell.cpp index 1c0f937..73eda84 100644 --- a/Cell.cpp +++ b/Cell.cpp @@ -1,25 +1,25 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - + + //--------------------------------------------------------------------------- #include "Cell.h" @@ -30,101 +30,82 @@ // Cell functions -Cell::Cell(int xx,int yy,intptr patch,int hab) +Cell::Cell(int xx,int yy,Patch *patch,int hab) { -x = xx; y = yy; -pPatch = patch; -envVal = 1.0; // default - no effect of any gradient -envDev = eps = 0.0; -habIxx.push_back(hab); -#if RSDEBUG -//DebugGUI(("Cell::Cell(): this=" + Int2Str((int)this) -// + " x=" + Int2Str(x) + " y=" + Int2Str(y) -// + " habIndex=" + Int2Str(habIndex) -//).c_str()); -#endif -visits = 0; -smsData = 0; + x = xx; y = yy; + pPatch = patch; + envVal = 1.0; // default - no effect of any gradient + envDev = eps = 0.0; + habIxx.push_back(hab); + visits = 0; + smsData = 0; } -Cell::Cell(int xx,int yy,intptr patch,float hab) +Cell::Cell(int xx,int yy,Patch *patch,float hab) { -x = xx; y = yy; -pPatch = patch; -envVal = 1.0; // default - no effect of any gradient -envDev = eps = 0.0; -habitats.push_back(hab); -visits = 0; -smsData = 0; + x = xx; y = yy; + pPatch = patch; + envVal = 1.0; // default - no effect of any gradient + envDev = eps = 0.0; + habitats.push_back(hab); + visits = 0; + smsData = 0; } Cell::~Cell() { -#if RSDEBUG -//DEBUGLOG << "Cell::~Cell(): this = " << this << " smsData = " << smsData << endl; -#endif -habIxx.clear(); -habitats.clear(); -if (smsData != 0) { - if (smsData->effcosts != 0) delete smsData->effcosts; - delete smsData; -} + habIxx.clear(); + habitats.clear(); + if (smsData != 0) { + if (smsData->effcosts != 0) delete smsData->effcosts; + delete smsData; + } +demoScalings.clear(); + #if RSDEBUG //DEBUGLOG << "Cell::~Cell(): deleted" << endl; #endif } void Cell::setHabIndex(short hx) { -#if RSDEBUG -//DebugGUI(("Cell::setHabIndex(): this=" + Int2Str((int)this) -// + " x=" + Int2Str(x) + " y=" + Int2Str(y) -// + " habIx=" + Int2Str(habIx) -//).c_str()); -#endif -if (hx < 0) habIxx.push_back(0); -else habIxx.push_back(hx); + if (hx < 0) habIxx.push_back(0); + else habIxx.push_back(hx); } void Cell::changeHabIndex(short ix,short hx) { -if (ix >= 0 && ix < (short)habIxx.size() && hx >= 0) habIxx[ix] = hx; -else habIxx[ix] = 0; + if (ix >= 0 && ix < (short)habIxx.size() && hx >= 0) habIxx[ix] = hx; + else habIxx[ix] = 0; } int Cell::getHabIndex(int ix) { -if (ix < 0 || ix >= (int)habIxx.size()) - // nodata cell OR should not occur, but treat as such - return -1; -else return habIxx[ix]; + if (ix < 0 || ix >= (int)habIxx.size()) + // nodata cell OR should not occur, but treat as such + return -1; + else return habIxx[ix]; } int Cell::nHabitats(void) { -int nh = (int)habIxx.size(); -if ((int)habitats.size() > nh) nh = (int)habitats.size(); -return nh; + int nh = (int)habIxx.size(); + if ((int)habitats.size() > nh) nh = (int)habitats.size(); + return nh; } void Cell::setHabitat(float q) { -if (q >= 0.0 && q <= 100.0) habitats.push_back(q); -else habitats.push_back(0.0); + if (q >= 0.0 && q <= 100.0) habitats.push_back(q); + else habitats.push_back(0.0); } float Cell::getHabitat(int ix) { -if (ix < 0 || ix >= (int)habitats.size()) - // nodata cell OR should not occur, but treat as such - return -1.0; -else return habitats[ix]; + if (ix < 0 || ix >= (int)habitats.size()) + // nodata cell OR should not occur, but treat as such + return -1.0; + else return habitats[ix]; } -void Cell::setPatch(intptr p) { -pPatch = p; +void Cell::setPatch(Patch *p) { + pPatch = p; } -intptr Cell::getPatch(void) +Patch *Cell::getPatch(void) { -#if RSDEBUG -//DebugGUI(("Cell::getPatch(): this=" + Int2Str((int)this) -// + " x=" + Int2Str(x) + " y=" + Int2Str(y) -// + " habIxx[0]=" + Int2Str(habIxx[0]) + " pPatch=" + Int2Str(pPatch) -//).c_str()); -#endif -return pPatch; + return pPatch; } locn Cell::getLocn(void) { locn q; q.x = x; q.y = y; return q; } @@ -134,79 +115,104 @@ void Cell::setEnvDev(float d) { envDev = d; } float Cell::getEnvDev(void) { return envDev; } void Cell::setEnvVal(float e) { -if (e >= 0.0) envVal = e; + if (e >= 0.0) envVal = e; } float Cell::getEnvVal(void) { return envVal; } void Cell::updateEps(float ac,float randpart) { -eps = eps*ac + randpart; + eps = eps * ac + randpart; } float Cell::getEps(void) { return eps; } // Functions to handle costs for SMS +#ifdef _OPENMP +std::unique_lock Cell::lockCost() { + return std::unique_lock(cost_mutex); +} +#endif + int Cell::getCost(void) { -int c; -if (smsData == 0) c = 0; // costs not yet set up -else c = smsData->cost; -return c; + int c; + if (smsData == 0) c = 0; // costs not yet set up + else c = smsData->cost; + return c; } void Cell::setCost(int c) { -if (smsData == 0) { - smsData = new smscosts; - smsData->effcosts = 0; -} -smsData->cost = c; + if (smsData == 0) { + smsData = new smscosts; + smsData->effcosts = 0; + } + smsData->cost = c; } // Reset the cost and the effective cost of the cell void Cell::resetCost(void) { -if (smsData != 0) { resetEffCosts(); delete smsData; } -smsData = 0; + if (smsData != 0) { resetEffCosts(); delete smsData; } + smsData = 0; } array3x3f Cell::getEffCosts(void) { -array3x3f a; -if (smsData == 0 || smsData->effcosts == 0) { // effective costs have not been calculated - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - a.cell[i][j] = -1.0; + array3x3f a; + if (smsData == 0 || smsData->effcosts == 0) { // effective costs have not been calculated + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + a.cell[i][j] = -1.0; + } } } -} -else - a = *smsData->effcosts; -return a; + else + a = *smsData->effcosts; + return a; } -void Cell::setEffCosts(array3x3f a) { -if (smsData->effcosts == 0) smsData->effcosts = new array3x3f; -*smsData->effcosts = a; +void Cell::setEffCosts(array3x3f a) { + if (smsData->effcosts == 0) smsData->effcosts = new array3x3f; + *smsData->effcosts = a; } // Reset the effective cost, but not the cost, of the cell void Cell::resetEffCosts(void) { -if (smsData != 0) { - if (smsData->effcosts != 0) { - delete smsData->effcosts; - smsData->effcosts = 0; + if (smsData != 0) { + if (smsData->effcosts != 0) { + delete smsData->effcosts; + smsData->effcosts = 0; + } } } -} void Cell::resetVisits(void) { visits = 0; } void Cell::incrVisits(void) { visits++; } unsigned long int Cell::getVisits(void) { return visits; } + +void Cell::addchgDemoScaling(std::vector ds) { + std::for_each(ds.begin(), ds.end(), [](float& perc){ if(perc < 0.0 || perc > 100.0) perc=100; }); + demoScalings.push_back(ds); + return; +} + +std::vector Cell::getDemoScaling(short chgyear) { + if (chgyear < 0 || chgyear >= (int)demoScalings.size()) { + std::vector ret(1, -1); + return ret; + } + else return demoScalings[chgyear]; +} + + + //--------------------------------------------------------------------------- // Initial species distribution cell functions DistCell::DistCell(int xx,int yy) { -x = xx; y = yy; initialise = false; + x = xx; + y = yy; + initialise = false; } DistCell::~DistCell() { @@ -214,18 +220,21 @@ DistCell::~DistCell() { } void DistCell::setCell(bool init) { -initialise = init; + initialise = init; } bool DistCell::toInitialise(locn loc) { -if (loc.x == x && loc.y == y) return initialise; -else return false; + if (loc.x == x && loc.y == y) return initialise; + else return false; } bool DistCell::selected(void) { return initialise; } locn DistCell::getLocn(void) { -locn loc; loc.x = x; loc.y = y; return loc; + locn loc; + loc.x = x; + loc.y = y; + return loc; } //--------------------------------------------------------------------------- diff --git a/Cell.h b/Cell.h index 5382a1e..2dc28f8 100644 --- a/Cell.h +++ b/Cell.h @@ -1,74 +1,84 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - -/*------------------------------------------------------------------------------ -RangeShifter v2.0 Cell -Implements the following classes: + /*------------------------------------------------------------------------------ -Cell - Landscape cell + RangeShifter v2.0 Cell -DistCell - Initial species distribution cell + Implements the following classes: -For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. -and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. -Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 + Cell - Landscape cell -Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + DistCell - Initial species distribution cell -Last updated: 14 January 2021 by Steve Palmer + For full details of RangeShifter, please see: + Bocedi G., Palmer S.C.F., Pe’er G., Heikkinen R.K., Matsinos Y.G., Watts K. + and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial + eco-evolutionary dynamics and species’ responses to environmental changes. + Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 -------------------------------------------------------------------------------*/ + Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + + Last updated: 14 January 2021 by Steve Palmer + + ------------------------------------------------------------------------------*/ #ifndef CellH #define CellH + +#include + #include using namespace std; #include "Parameters.h" +#ifdef _OPENMP +#include +#include +#endif + //--------------------------------------------------------------------------- +class Patch; // Forward-declaration of the Patch class + struct array3x3f { float cell[3][3]; }; // neighbourhood cell array (SMS) -struct smscosts { int cost; array3x3f *effcosts; }; // cell costs for SMS +struct smscosts { int cost; array3x3f* effcosts; }; // cell costs for SMS // Landscape cell -class Cell{ +class Cell { public: Cell( // Constructor for habitat codes int, // x co-ordinate int, // y co-ordinate - intptr, // pointer (cast as integer) to the Patch to which Cell belongs + Patch *, // pointer to the Patch to which Cell belongs int // habitat index number ); Cell( // Constructor for habitat % cover or habitat quality int, // x co-ordinate int, // y co-ordinate - intptr, // pointer (cast as integer) to the Patch to which Cell belongs + Patch *, // pointer to the Patch to which Cell belongs float // habitat proportion or cell quality score ); ~Cell(); @@ -90,9 +100,9 @@ class Cell{ int // habitat index number / landscape change number ); void setPatch( - intptr // pointer (cast as integer) to the Patch to which Cell belongs + Patch * // pointer to the Patch to which Cell belongs ); - intptr getPatch(void); + Patch *getPatch(void); locn getLocn(void); void setEnvDev( float // local environmental deviation @@ -110,6 +120,9 @@ class Cell{ void setCost( int // cost value for SMS ); +#ifdef _OPENMP + std::unique_lock lockCost(void); +#endif int getCost(void); void resetCost(void); array3x3f getEffCosts(void); @@ -121,29 +134,44 @@ class Cell{ void incrVisits(void); unsigned long int getVisits(void); + void addchgDemoScaling(std::vector); + void setDemoScaling(std::vector, short); + std::vector getDemoScaling(short); + + private: - int x,y; // cell co-ordinates - intptr pPatch; // pointer (cast as integer) to the Patch to which cell belongs + int x, y; // cell co-ordinates + Patch *pPatch; // pointer to the Patch to which cell belongs // NOTE: THE FOLLOWING ENVIRONMENTAL VARIABLES COULD BE COMBINED IN A STRUCTURE // AND ACCESSED BY A POINTER ... float envVal; // environmental value, representing one of: - // gradient in K, r or extinction probability + // gradient in K, r or extinction probability float envDev; // local environmental deviation (static, in range -1.0 to +1.0) float eps; // local environmental stochasticity (epsilon) (dynamic, from N(0,std)) +#ifdef _OPENMP + std::atomic visits; // no. of times square is visited by dispersers +#else unsigned long int visits; // no. of times square is visited by dispersers - smscosts *smsData; +#endif + smscosts* smsData; vector habIxx; // habitat indices (rasterType=0) - // NB initially, habitat codes are loaded, then converted to index nos. - // once landscape is fully loaded + // NB initially, habitat codes are loaded, then converted to index nos. + // once landscape is fully loaded vector habitats; // habitat proportions (rasterType=1) or quality (rasterType=2) + + std::vector> demoScalings; // demographic scaling layers (only if rasterType==2) + +#ifdef _OPENMP + std::mutex cost_mutex; +#endif }; //--------------------------------------------------------------------------- // Initial species distribution cell -class DistCell{ +class DistCell { public: DistCell( int, // x co-ordinate @@ -160,15 +188,11 @@ class DistCell{ locn getLocn(void); private: - int x,y; // cell co-ordinates + int x, y; // cell co-ordinates bool initialise; // cell is to be initialised }; -#if RSDEBUG -extern void DebugGUI(string); -#endif - //--------------------------------------------------------------------------- #endif diff --git a/Community.cpp b/Community.cpp index 286dc6a..46c8970 100644 --- a/Community.cpp +++ b/Community.cpp @@ -1,6 +1,7 @@ +#include "Community.h" /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -24,18 +25,41 @@ #include "Community.h" +#ifdef _OPENMP +#ifdef __has_include +#if __has_include() +#include +#endif +#endif +#include +#if __cpp_lib_barrier >= 201907L && __cpp_lib_optional >= 201606L +#define HAS_BARRIER_LIB +#include +#include +#else +#include +#include +#endif +#include +#endif // _OPENMP + //--------------------------------------------------------------------------- ofstream outrange; ofstream outoccup, outsuit; ofstream outtraitsrows; +ofstream ofsGenes; +ofstream outwcfstat; +ofstream outperlocusfstat; +ofstream outpairwisefst; //--------------------------------------------------------------------------- Community::Community(Landscape* pLand) { pLandscape = pLand; indIx = 0; + pNeutralStatistics = 0; } Community::~Community(void) { @@ -54,13 +78,11 @@ SubCommunity* Community::addSubComm(Patch* pPch, int num) { void Community::initialise(Species* pSpecies, int year) { - int nsubcomms, npatches, ndistcells, spratio, patchnum, rr = 0; locn distloc; patchData pch; patchLimits limits; - intptr ppatch, subcomm; - std::vector subcomms; + std::vector subcomms; std::vector selected; SubCommunity* pSubComm; Patch* pPatch; @@ -72,16 +94,6 @@ void Community::initialise(Species* pSpecies, int year) spratio = ppLand.spResol / ppLand.resol; -#if RSDEBUG - DEBUGLOG << endl << "Community::initialise(): this=" << this - << " seedType=" << init.seedType << " freeType=" << init.freeType - << " minSeedX=" << init.minSeedX << " minSeedY=" << init.minSeedY - << " maxSeedX=" << init.maxSeedX << " maxSeedY=" << init.maxSeedY - << " indsFile=" << init.indsFile - << " nsubcomms=" << nsubcomms << " spratio=" << spratio - << endl; -#endif - switch (init.seedType) { case 0: // free initialisation @@ -138,7 +150,7 @@ void Community::initialise(Species* pSpecies, int year) } for (int i = 0; i < npatches; i++) { if (selected[i]) { - pSubComm = (SubCommunity*)subcomms[i]; + pSubComm = subcomms[i]; pSubComm->setInitial(true); } } @@ -155,14 +167,11 @@ void Community::initialise(Species* pSpecies, int year) if (patchnum != 0) { if (pch.pPatch->getK() > 0.0) { // patch is suitable - subcomm = pch.pPatch->getSubComm(); - if (subcomm == 0) { + pSubComm = pch.pPatch->getSubComm(); + if (pSubComm == nullptr) { // create a sub-community in the patch pSubComm = addSubComm(pch.pPatch, patchnum); } - else { - pSubComm = (SubCommunity*)subcomm; - } pSubComm->setInitial(true); } } @@ -211,13 +220,11 @@ void Community::initialise(Species* pSpecies, int year) for (int y = 0; y < spratio; y++) { pCell = pLandscape->findCell(distloc.x * spratio + x, distloc.y * spratio + y); if (pCell != 0) { // not a no-data cell - ppatch = pCell->getPatch(); - if (ppatch != 0) { - pPatch = (Patch*)ppatch; + pPatch = pCell->getPatch(); + if (pPatch != nullptr) { if (pPatch->getSeqNum() != 0) { // not the matrix patch - subcomm = pPatch->getSubComm(); - if (subcomm != 0) { - pSubComm = (SubCommunity*)subcomm; + pSubComm = pPatch->getSubComm(); + if (pSubComm != nullptr) { pSubComm->setInitial(true); } } @@ -246,7 +253,8 @@ void Community::initialise(Species* pSpecies, int year) indIx = 0; // reset index for initial individuals } else { // add any initial individuals for the current year - initInd iind; iind.year = 0; + initInd iind = initInd(); + iind.year = 0; int ninds = paramsInit->numInitInds(); while (indIx < ninds && iind.year <= year) { iind = paramsInit->getInitInd(indIx); @@ -256,14 +264,11 @@ void Community::initialise(Species* pSpecies, int year) pPatch = pLandscape->findPatch(iind.patchID); if (pPatch->getK() > 0.0) { // patch is suitable - subcomm = pPatch->getSubComm(); - if (subcomm == 0) { + pSubComm = pPatch->getSubComm(); + if (pSubComm == nullptr) { // create a sub-community in the patch pSubComm = addSubComm(pPatch, iind.patchID); } - else { - pSubComm = (SubCommunity*)subcomm; - } pSubComm->initialInd(pLandscape, pSpecies, pPatch, pPatch->getRandomCell(), indIx); } } @@ -271,19 +276,15 @@ void Community::initialise(Species* pSpecies, int year) else { // cell-based model pCell = pLandscape->findCell(iind.x, iind.y); if (pCell != 0) { - intptr ppatch = pCell->getPatch(); - if (ppatch != 0) { - pPatch = (Patch*)ppatch; + pPatch = pCell->getPatch(); + if (pPatch != nullptr) { if (pPatch->getK() > 0.0) { // patch is suitable - subcomm = pPatch->getSubComm(); - if (subcomm == 0) { + pSubComm = pPatch->getSubComm(); + if (pSubComm == nullptr) { // create a sub-community in the patch pSubComm = addSubComm(pPatch, iind.patchID); } - else { - pSubComm = (SubCommunity*)subcomm; - } pSubComm->initialInd(pLandscape, pSpecies, pPatch, pCell, indIx); } } @@ -308,18 +309,11 @@ void Community::initialise(Species* pSpecies, int year) } // end of switch (init.seedType) -#if RSDEBUG - DEBUGLOG << "Community::initialise(): this=" << this - << " nsubcomms=" << nsubcomms - << endl; -#endif - } // Add manually selected patches/cells to the selected set for initialisation void Community::addManuallySelected(void) { int npatches; - intptr subcomm, patch; locn initloc; Cell* pCell; Patch* pPatch; @@ -328,19 +322,14 @@ void Community::addManuallySelected(void) { landParams ppLand = pLandscape->getLandParams(); npatches = pLandscape->initCellCount(); // no. of patches/cells specified -#if RSDEBUG - DEBUGLOG << "Community::addManuallySelected(): this = " << this - << " npatches = " << npatches << endl; -#endif // identify sub-communities to be initialised if (ppLand.patchModel) { for (int i = 0; i < npatches; i++) { initloc = pLandscape->getInitCell(i); // patch number held in x-coord of list pPatch = pLandscape->findPatch(initloc.x); if (pPatch != 0) { - subcomm = pPatch->getSubComm(); - if (subcomm != 0) { - pSubComm = (SubCommunity*)subcomm; + pSubComm = pPatch->getSubComm(); + if (pSubComm != nullptr) { pSubComm->setInitial(true); } } @@ -353,29 +342,11 @@ void Community::addManuallySelected(void) { && initloc.y >= 0 && initloc.y < ppLand.dimY) { pCell = pLandscape->findCell(initloc.x, initloc.y); if (pCell != 0) { // not no-data cell - patch = pCell->getPatch(); -#if RSDEBUG - DEBUGLOG << "Community::initialise(): i = " << i - << " x = " << initloc.x << " y = " << initloc.y - << " pCell = " << pCell << " patch = " << patch - << endl; -#endif - if (patch != 0) { - pPatch = (Patch*)patch; - subcomm = pPatch->getSubComm(); -#if RSDEBUG - DEBUGLOG << "Community::initialise(): i = " << i - << " pPatch = " << pPatch << " subcomm = " << subcomm - << endl; -#endif - if (subcomm != 0) { - pSubComm = (SubCommunity*)subcomm; + pPatch = pCell->getPatch(); + if (pPatch != nullptr) { + pSubComm = pPatch->getSubComm(); + if (pSubComm != nullptr) { pSubComm->setInitial(true); -#if RSDEBUG - DEBUGLOG << "Community::initialise(): i = " << i - << " pSubComm = " << pSubComm - << endl; -#endif } } } @@ -386,6 +357,7 @@ void Community::addManuallySelected(void) { void Community::resetPopns(void) { int nsubcomms = (int)subComms.size(); + #pragma omp parallel for schedule(static,128) for (int i = 0; i < nsubcomms; i++) { // all sub-communities subComms[i]->resetPopns(); } @@ -413,49 +385,93 @@ void Community::patchChanges(void) { void Community::reproduction(int yr) { - float eps = 0.0; // epsilon for environmental stochasticity landParams land = pLandscape->getLandParams(); envStochParams env = paramsStoch->getStoch(); + float eps = 0.0; // epsilon for environmental stochasticity + if (env.stoch && !env.local) { // global stochasticty + eps = pLandscape->getGlobalStoch(yr); + } int nsubcomms = (int)subComms.size(); -#if RSDEBUG - DEBUGLOG << "Community::reproduction(): this=" << this - << " nsubcomms=" << nsubcomms << endl; -#endif + #pragma omp parallel for schedule(static,128) for (int i = 0; i < nsubcomms; i++) { // all sub-communities - if (env.stoch) { - if (!env.local) { // global stochasticty - eps = pLandscape->getGlobalStoch(yr); - } - } subComms[i]->reproduction(land.resol, eps, land.rasterType, land.patchModel); } -#if RSDEBUG - DEBUGLOG << "Community::reproduction(): finished" << endl; -#endif } void Community::emigration(void) { - int nsubcomms = (int)subComms.size(); -#if RSDEBUG - DEBUGLOG << "Community::emigration(): this=" << this - << " nsubcomms=" << nsubcomms << endl; -#endif + int nsubcomms = static_cast(subComms.size()); + #pragma omp parallel for schedule(static, 128) for (int i = 0; i < nsubcomms; i++) { // all sub-communities subComms[i]->emigration(); } -#if RSDEBUG - DEBUGLOG << "Community::emigration(): finished" << endl; -#endif } -#if RS_RCPP // included also SEASONAL +#ifdef _OPENMP +#ifdef HAS_BARRIER_LIB +typedef std::optional> split_barrier; +#else +class split_barrier { +private: + std::mutex m; + std::condition_variable cv; + int threads_in_section; + int total_threads; + bool may_enter; + bool may_leave; + +public: + split_barrier(): + threads_in_section(0), + may_enter(false), + may_leave(false) + {} + + void emplace(int threads) { + std::lock_guard lock(m); + total_threads = threads; + may_enter = true; + } + + void enter() { + std::unique_lock lock(m); + cv.wait(lock, [this]{return may_enter;}); + if (++threads_in_section == total_threads) { + may_enter = false; + may_leave = true; + lock.unlock(); + cv.notify_all(); + } + } + + void leave() { + std::unique_lock lock(m); + cv.wait(lock, [this]{return may_leave;}); + if (--threads_in_section == 0) { + may_leave = false; + may_enter = true; + lock.unlock(); + cv.notify_all(); + } + } +}; +#endif // HAS_BARRIER_LIB +#endif // _OPENMP + +#if RS_RCPP void Community::dispersal(short landIx, short nextseason) #else void Community::dispersal(short landIx) -#endif // SEASONAL || RS_RCPP +#endif // RS_RCPP { +#ifdef _OPENMP + std::atomic nbStillDispersing; + split_barrier barrier; +#else + int nbStillDispersing; +#endif // _OPENMP + #if RSDEBUG int t0, t1, t2; t0 = time(0); @@ -466,43 +482,89 @@ void Community::dispersal(short landIx) int nsubcomms = (int)subComms.size(); // initiate dispersal - all emigrants leave their natal community and join matrix community SubCommunity* matrix = subComms[0]; // matrix community is always the first - for (int i = 0; i < nsubcomms; i++) { // all populations - subComms[i]->initiateDispersal(matrix); - } -#if RSDEBUG - t1 = time(0); - DEBUGLOG << "Community::dispersal(): this=" << this - << " nsubcomms=" << nsubcomms << " initiation time=" << t1 - t0 << endl; -#endif +#pragma omp parallel + { + std::map> disperserPool; - // dispersal is undertaken by all individuals now in the matrix patch - // (even if not physically in the matrix) - int ndispersers = 0; - do { - for (int i = 0; i < nsubcomms; i++) { // all populations - subComms[i]->resetPossSettlers(); + // All individuals in the matrix disperse again + // (= unsettled dispersers from previous generation) + matrix->disperseMatrix(disperserPool); + + // Recruit new emigrants +#pragma omp for schedule(static,128) nowait + for (int i = 0; i < nsubcomms; i++) { + subComms[i]->recruitDispersers(disperserPool); } -#if RS_RCPP // included also SEASONAL - ndispersers = matrix->transfer(pLandscape, landIx, nextseason); + +#ifdef _OPENMP +#pragma omp single + barrier.emplace(omp_get_num_threads()); +#endif // _OPENMP + + + // + do { +#pragma omp for schedule(guided) + for (int i = 0; i < nsubcomms; i++) { + subComms[i]->resetPossSettlers(); + } + int localNbDispersers = matrix->resolveTransfer(disperserPool, pLandscape, landIx); +#pragma omp single nowait + nbStillDispersing = 0; +#pragma omp barrier +#if RS_RCPP + localNbDispersers += matrix->resolveSettlement(disperserPool, pLandscape, nextseason); #else - ndispersers = matrix->transfer(pLandscape, landIx); -#endif // SEASONAL || RS_RCPP - matrix->completeDispersal(pLandscape, sim.outConnect); - } while (ndispersers > 0); + localNbDispersers += matrix->resolveSettlement(disperserPool, pLandscape); +#endif // RS_RCPP + nbStillDispersing += localNbDispersers; + +#ifdef _OPENMP +#ifdef HAS_BARRIER_LIB + std::barrier<>::arrival_token token = barrier->arrive(); +#else + barrier.enter(); +#endif // HAS_BARRIER_LIB +#endif // _OPENMP + + matrix->completeDispersal(disperserPool, pLandscape, sim.outConnect); + +#ifdef _OPENMP +#ifdef HAS_BARRIER_LIB + barrier->wait(std::move(token)); +#else + barrier.leave(); +#endif // HAS_BARRIER_LIB +#endif // _OPENMP + + } while (nbStillDispersing > 0); + + // All unsettled dispersers are stored in matrix until next generation + for (auto & it : disperserPool) { + Species* const& pSpecies = it.first; + vector& inds = it.second; + matrix->recruitMany(inds, pSpecies); + } + } -#if RSDEBUG - DEBUGLOG << "Community::dispersal(): matrix=" << matrix << endl; - t2 = time(0); - DEBUGLOG << "Community::dispersal(): transfer time=" << t2 - t1 << endl; -#endif } -void Community::survival(short part, short option0, short option1) +void Community::survival0(short option0, short option1) { int nsubcomms = (int)subComms.size(); + #pragma omp parallel for schedule(static,128) for (int i = 0; i < nsubcomms; i++) { // all communities (including in matrix) - subComms[i]->survival(part, option0, option1); + subComms[i]->survival0(option0, option1); + } +} + +void Community::survival1() +{ + int nsubcomms = (int)subComms.size(); + #pragma omp parallel for schedule(static,128) + for (int i = 0; i < nsubcomms; i++) { // all communities (including in matrix) + subComms[i]->survival1(); } } @@ -553,17 +615,12 @@ void Community::createOccupancy(int nrows, int reps) { void Community::updateOccupancy(int row, int rep) { -#if RSDEBUG - DEBUGLOG << "Community::updateOccupancy(): row=" << row << endl; -#endif int nsubcomms = (int)subComms.size(); for (int i = 0; i < nsubcomms; i++) { subComms[i]->updateOccupancy(row); } - commStats s = getStats(); occSuit[row][rep] = (float)s.occupied / (float)s.suitable; - } void Community::deleteOccupancy(int nrows) { @@ -583,7 +640,7 @@ void Community::deleteOccupancy(int nrows) { // Determine range margins commStats Community::getStats(void) { - commStats s; + commStats s = commStats(); landParams ppLand = pLandscape->getLandParams(); s.ninds = s.nnonjuvs = s.suitable = s.occupied = 0; s.minX = ppLand.maxX; s.minY = ppLand.maxY; s.maxX = s.maxY = 0; @@ -616,9 +673,14 @@ commStats Community::getStats(void) // Functions to control production of output files +// Close population file +bool Community::outPopFinishLandscape() { + return subComms[0]->outPopFinishLandscape(); +} + // Open population file and write header record -bool Community::outPopHeaders(Species* pSpecies, int option) { - return subComms[0]->outPopHeaders(pLandscape, pSpecies, option); +bool Community::outPopStartLandscape(Species* pSpecies) { + return subComms[0]->outPopStartLandscape(pLandscape, pSpecies); } // Write records to population file @@ -632,53 +694,36 @@ void Community::outPop(int rep, int yr, int gen) } +// Close individuals file +void Community::outIndsFinishReplicate() { + subComms[0]->outIndsFinishReplicate(); +} -// Write records to individuals file -void Community::outInds(int rep, int yr, int gen, int landNr) { +// Open individuals file and write header record +void Community::outIndsStartReplicate(int rep, int landNr) { + subComms[0]->outIndsStartReplicate(pLandscape, rep, landNr); +} - if (landNr >= 0) { // open the file - subComms[0]->outInds(pLandscape, rep, yr, gen, landNr); - return; - } - if (landNr == -999) { // close the file - subComms[0]->outInds(pLandscape, rep, yr, gen, -999); - return; - } +// Write records to individuals file +void Community::outIndividuals(int rep, int yr, int gen) { // generate output for each sub-community (patch) in the community int nsubcomms = (int)subComms.size(); for (int i = 0; i < nsubcomms; i++) { // all sub-communities - subComms[i]->outInds(pLandscape, rep, yr, gen, landNr); + subComms[i]->outIndividuals(pLandscape, rep, yr, gen); } } -// Write records to genetics file -void Community::outGenetics(int rep, int yr, int gen, int landNr) { - //landParams ppLand = pLandscape->getLandParams(); - if (landNr >= 0) { // open the file - subComms[0]->outGenetics(rep, yr, gen, landNr); - return; - } - if (landNr == -999) { // close the file - subComms[0]->outGenetics(rep, yr, gen, landNr); - return; - } - // generate output for each sub-community (patch) in the community - int nsubcomms = (int)subComms.size(); - for (int i = 0; i < nsubcomms; i++) { // all sub-communities - subComms[i]->outGenetics(rep, yr, gen, landNr); - } +// Close range file +bool Community::outRangeFinishLandscape() +{ + if (outrange.is_open()) outrange.close(); + outrange.clear(); + return true; } // Open range file and write header record -bool Community::outRangeHeaders(Species* pSpecies, int landNr) +bool Community::outRangeStartLandscape(Species* pSpecies, int landNr) { - - if (landNr == -999) { // close the file - if (outrange.is_open()) outrange.close(); - outrange.clear(); - return true; - } - string name; landParams ppLand = pLandscape->getLandParams(); envStochParams env = paramsStoch->getStoch(); @@ -686,33 +731,21 @@ bool Community::outRangeHeaders(Species* pSpecies, int landNr) // NEED TO REPLACE CONDITIONAL COLUMNS BASED ON ATTRIBUTES OF ONE SPECIES TO COVER // ATTRIBUTES OF *ALL* SPECIES AS DETECTED AT MODEL LEVEL - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); -#if RSDEBUG - DEBUGLOG << "Community::outRangeHeaders(): simulation=" << sim.simulation - << " sim.batchMode=" << sim.batchMode - << " landNr=" << landNr << endl; -#endif - if (sim.batchMode) { name = paramsSim->getDir(2) -#if RS_RCPP - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) + "_Land" - + Int2Str(landNr) -#else - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) + "_Land" - + Int2Str(landNr) -#endif + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + + to_string(landNr) + "_Range.txt"; } else { - name = paramsSim->getDir(2) + "Sim" + Int2Str(sim.simulation) + "_Range.txt"; + name = paramsSim->getDir(2) + "Sim" + to_string(sim.simulation) + "_Range.txt"; } outrange.open(name.c_str()); outrange << "Rep\tYear\tRepSeason"; @@ -749,7 +782,7 @@ bool Community::outRangeHeaders(Species* pSpecies, int landNr) } } if (trfr.indVar) { - if (trfr.moveModel) { + if (trfr.usesMovtProc) { if (trfr.moveType == 1) { outrange << "\tmeanDP\tstdDP\tmeanGB\tstdGB"; outrange << "\tmeanAlphaDB\tstdAlphaDB\tmeanBetaDB\tstdBetaDB"; @@ -787,31 +820,21 @@ bool Community::outRangeHeaders(Species* pSpecies, int landNr) } } outrange << endl; - -#if RSDEBUG - DEBUGLOG << "Community::outRangeHeaders(): finished" << endl; -#endif - return outrange.is_open(); } // Write record to range file void Community::outRange(Species* pSpecies, int rep, int yr, int gen) { -#if RSDEBUG - DEBUGLOG << "Community::outRange(): rep=" << rep - << " yr=" << yr << " gen=" << gen << endl; -#endif - landParams ppLand = pLandscape->getLandParams(); envStochParams env = paramsStoch->getStoch(); // NEED TO REPLACE CONDITIONAL COLUMNS BASED ON ATTRIBUTES OF ONE SPECIES TO COVER // ATTRIBUTES OF *ALL* SPECIES AS DETECTED AT MODEL LEVEL - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); outrange << rep << "\t" << yr << "\t" << gen; @@ -828,14 +851,14 @@ void Community::outRange(Species* pSpecies, int rep, int yr, int gen) for (int stg = 1; stg < sstruct.nStages; stg++) { stagepop = 0; for (int i = 0; i < nsubcomms; i++) { // all sub-communities - stagepop += subComms[i]->stagePop(stg); + stagepop += subComms[i]->getNbInds(stg); } outrange << "\t" << stagepop; } // juveniles born in current reproductive season stagepop = 0; for (int i = 0; i < nsubcomms; i++) { // all sub-communities - stagepop += subComms[i]->stagePop(0); + stagepop += subComms[i]->getNbInds(0); } outrange << "\t" << stagepop; } @@ -858,14 +881,11 @@ void Community::outRange(Species* pSpecies, int rep, int yr, int gen) outrange << "\t0\t0\t0\t0"; if (emig.indVar || trfr.indVar || sett.indVar) { // output trait means - traitsums ts; + traitsums ts = traitsums(); traitsums scts; // sub-community traits - traitCanvas tcanv; int ngenes, popsize; - tcanv.pcanvas[0] = NULL; - - for (int i = 0; i < NSEXES; i++) { + for (int i = 0; i < gMaxNbSexes; i++) { ts.ninds[i] = 0; ts.sumD0[i] = ts.ssqD0[i] = 0.0; ts.sumAlpha[i] = ts.ssqAlpha[i] = 0.0; ts.sumBeta[i] = ts.ssqBeta[i] = 0.0; @@ -882,8 +902,8 @@ void Community::outRange(Species* pSpecies, int rep, int yr, int gen) int nsubcomms = (int)subComms.size(); for (int i = 0; i < nsubcomms; i++) { // all sub-communities (incl. matrix) - scts = subComms[i]->outTraits(tcanv, pLandscape, rep, yr, gen, true); - for (int j = 0; j < NSEXES; j++) { + scts = subComms[i]->outTraits(pLandscape, rep, yr, gen, true); + for (int j = 0; j < gMaxNbSexes; j++) { ts.ninds[j] += scts.ninds[j]; ts.sumD0[j] += scts.sumD0[j]; ts.ssqD0[j] += scts.ssqD0[j]; ts.sumAlpha[j] += scts.sumAlpha[j]; ts.ssqAlpha[j] += scts.ssqAlpha[j]; @@ -959,7 +979,7 @@ void Community::outRange(Species* pSpecies, int rep, int yr, int gen) } if (trfr.indVar) { - if (trfr.moveModel) { + if (trfr.usesMovtProc) { // CURRENTLY INDIVIDUAL VARIATION CANNOT BE SEX-DEPENDENT ngenes = 1; } @@ -1016,7 +1036,7 @@ void Community::outRange(Species* pSpecies, int rep, int yr, int gen) } } } - if (trfr.moveModel) { + if (trfr.usesMovtProc) { if (trfr.moveType == 1) { outrange << "\t" << mnDP[0] << "\t" << sdDP[0]; outrange << "\t" << mnGB[0] << "\t" << sdGB[0]; @@ -1108,16 +1128,18 @@ void Community::outRange(Species* pSpecies, int rep, int yr, int gen) outrange << endl; } -// Open occupancy file, write header record and set up occupancy array -bool Community::outOccupancyHeaders(int option) +// Close occupancy file +bool Community::outOccupancyFinishLandscape() { - if (option == -999) { // close the files - if (outsuit.is_open()) outsuit.close(); - if (outoccup.is_open()) outoccup.close(); - outsuit.clear(); outoccup.clear(); - return true; - } + if (outsuit.is_open()) outsuit.close(); + if (outoccup.is_open()) outoccup.close(); + outsuit.clear(); outoccup.clear(); + return true; +} +// Open occupancy file, write header record and set up occupancy array +bool Community::outOccupancyStartLandscape() +{ string name, nameI; simParams sim = paramsSim->getSim(); landParams ppLand = pLandscape->getLandParams(); @@ -1125,22 +1147,22 @@ bool Community::outOccupancyHeaders(int option) name = paramsSim->getDir(2); if (sim.batchMode) { - name += "Batch" + Int2Str(sim.batchNum) + "_"; - name += "Sim" + Int2Str(sim.simulation) + "_Land" + Int2Str(ppLand.landNum); + name += "Batch" + to_string(sim.batchNum) + "_"; + name += "Sim" + to_string(sim.simulation) + "_Land" + to_string(ppLand.landNum); } else - name += "Sim" + Int2Str(sim.simulation); + name += "Sim" + to_string(sim.simulation); name += "_Occupancy_Stats.txt"; outsuit.open(name.c_str()); outsuit << "Year\tMean_OccupSuit\tStd_error" << endl; name = paramsSim->getDir(2); if (sim.batchMode) { - name += "Batch" + Int2Str(sim.batchNum) + "_"; - name += "Sim" + Int2Str(sim.simulation) + "_Land" + Int2Str(ppLand.landNum); + name += "Batch" + to_string(sim.batchNum) + "_"; + name += "Sim" + to_string(sim.simulation) + "_Land" + to_string(ppLand.landNum); } else - name += "Sim" + Int2Str(sim.simulation); + name += "Sim" + to_string(sim.simulation); name += "_Occupancy.txt"; outoccup.open(name.c_str()); if (ppLand.patchModel) { @@ -1181,9 +1203,10 @@ void Community::outOccupancy(void) { } } -void Community::outOccSuit(bool view) { +void Community::outOccSuit() { double sum, ss, mean, sd, se; simParams sim = paramsSim->getSim(); + for (int i = 0; i < (sim.years / sim.outIntOcc) + 1; i++) { sum = ss = 0.0; for (int rep = 0; rep < sim.reps; rep++) { @@ -1195,15 +1218,20 @@ void Community::outOccSuit(bool view) { if (sd > 0.0) sd = sqrt(sd); else sd = 0.0; se = sd / sqrt((double)(sim.reps)); + outsuit << i * sim.outIntOcc << "\t" << mean << "\t" << se << endl; - if (view) viewOccSuit(i * sim.outIntOcc, mean, se); } } +// Close traits file +bool Community::outTraitsFinishLandscape() { + return subComms[0]->outTraitsFinishLandscape(); +} + // Open traits file and write header record -bool Community::outTraitsHeaders(Species* pSpecies, int landNr) { - return subComms[0]->outTraitsHeaders(pLandscape, pSpecies, landNr); +bool Community::outTraitsStartLandscape(Species* pSpecies, int landNr) { + return subComms[0]->outTraitsStartLandscape(pLandscape, pSpecies, landNr); } // Write records to traits file @@ -1212,11 +1240,9 @@ only, this function relies on the fact that subcommunities are created in the sa sequence as patches, which is in asecending order of x nested within descending order of y */ -void Community::outTraits(traitCanvas tcanv, Species* pSpecies, - int rep, int yr, int gen) +void Community::outTraits(Species* pSpecies, int rep, int yr, int gen) { simParams sim = paramsSim->getSim(); - simView v = paramsSim->getViews(); landParams land = pLandscape->getLandParams(); traitsums* ts = 0; traitsums sctraits; @@ -1224,7 +1250,7 @@ void Community::outTraits(traitCanvas tcanv, Species* pSpecies, // create array of traits means, etc., one for each row ts = new traitsums[land.dimY]; for (int y = 0; y < land.dimY; y++) { - for (int i = 0; i < NSEXES; i++) { + for (int i = 0; i < gMaxNbSexes; i++) { ts[y].ninds[i] = 0; ts[y].sumD0[i] = ts[y].ssqD0[i] = 0.0; ts[y].sumAlpha[i] = ts[y].ssqAlpha[i] = 0.0; @@ -1237,22 +1263,22 @@ void Community::outTraits(traitCanvas tcanv, Species* pSpecies, ts[y].sumS0[i] = ts[y].ssqS0[i] = 0.0; ts[y].sumAlphaS[i] = ts[y].ssqAlphaS[i] = 0.0; ts[y].sumBetaS[i] = ts[y].ssqBetaS[i] = 0.0; + ts[y].sumGeneticFitness[i] = ts[y].ssqGeneticFitness[i] = 0.0; } } } - if (v.viewTraits - || ((sim.outTraitsCells && yr >= sim.outStartTraitCell && yr % sim.outIntTraitCell == 0) || - (sim.outTraitsRows && yr >= sim.outStartTraitRow && yr % sim.outIntTraitRow == 0))) + if ((sim.outTraitsCells && yr >= sim.outStartTraitCell && yr % sim.outIntTraitCell == 0) || + (sim.outTraitsRows && yr >= sim.outStartTraitRow && yr % sim.outIntTraitRow == 0)) { // generate output for each sub-community (patch) in the community int nsubcomms = (int)subComms.size(); for (int i = 1; i < nsubcomms; i++) { // // all except matrix sub-community - sctraits = subComms[i]->outTraits(tcanv, pLandscape, rep, yr, gen, false); + sctraits = subComms[i]->outTraits(pLandscape, rep, yr, gen, false); locn loc = subComms[i]->getLocn(); int y = loc.y; if (sim.outTraitsRows && yr >= sim.outStartTraitRow && yr % sim.outIntTraitRow == 0) { - for (int s = 0; s < NSEXES; s++) { + for (int s = 0; s < gMaxNbSexes; s++) { ts[y].ninds[s] += sctraits.ninds[s]; ts[y].sumD0[s] += sctraits.sumD0[s]; ts[y].ssqD0[s] += sctraits.ssqD0[s]; ts[y].sumAlpha[s] += sctraits.sumAlpha[s]; ts[y].ssqAlpha[s] += sctraits.ssqAlpha[s]; @@ -1265,6 +1291,7 @@ void Community::outTraits(traitCanvas tcanv, Species* pSpecies, ts[y].sumS0[s] += sctraits.sumS0[s]; ts[y].ssqS0[s] += sctraits.ssqS0[s]; ts[y].sumAlphaS[s] += sctraits.sumAlphaS[s]; ts[y].ssqAlphaS[s] += sctraits.ssqAlphaS[s]; ts[y].sumBetaS[s] += sctraits.sumBetaS[s]; ts[y].ssqBetaS[s] += sctraits.ssqBetaS[s]; + ts[y].sumGeneticFitness[s] += sctraits.sumGeneticFitness[s]; ts[y].ssqGeneticFitness[s] += sctraits.ssqGeneticFitness[s]; } } } @@ -1284,8 +1311,8 @@ void Community::outTraits(traitCanvas tcanv, Species* pSpecies, void Community::writeTraitsRows(Species* pSpecies, int rep, int yr, int gen, int y, traitsums ts) { - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); double mn, sd; @@ -1347,7 +1374,7 @@ void Community::writeTraitsRows(Species* pSpecies, int rep, int yr, int gen, int } if (trfr.indVar) { - if (trfr.moveModel) { + if (trfr.usesMovtProc) { if (trfr.moveType == 2) { // CRW // NB - CURRENTLY CANNOT BE SEX-DEPENDENT... if (popsize > 0) mn = ts.sumStepL[0] / (double)popsize; else mn = 0.0; @@ -1423,35 +1450,52 @@ void Community::writeTraitsRows(Species* pSpecies, int rep, int yr, int gen, int if (popsize > 1) sd = ts.ssqBetaS[0] / (double)popsize - mn * mn; else sd = 0.0; if (sd > 0.0) sd = sqrt(sd); else sd = 0.0; outtraitsrows << "\t" << mn << "\t" << sd; - // } } + if (pSpecies->getNbGenLoadTraits() > 0) { + if (gMaxNbSexes > 1) { + if (ts.ninds[0] > 0) mn = ts.sumGeneticFitness[0] / (double)ts.ninds[0]; else mn = 0.0; + if (ts.ninds[0] > 1) sd = ts.ssqGeneticFitness[0] / (double)ts.ninds[0] - mn * mn; else sd = 0.0; + if (sd > 0.0) sd = sqrt(sd); else sd = 0.0; + outtraitsrows << "\t" << mn << "\t" << sd; + if (ts.ninds[1] > 0) mn = ts.sumGeneticFitness[1] / (double)ts.ninds[1]; else mn = 0.0; + if (ts.ninds[1] > 1) sd = ts.ssqGeneticFitness[1] / (double)ts.ninds[1] - mn * mn; else sd = 0.0; + if (sd > 0.0) sd = sqrt(sd); else sd = 0.0; + outtraitsrows << "\t" << mn << "\t" << sd; + } + else { + if (ts.ninds[0] > 0) mn = ts.sumGeneticFitness[0] / (double)ts.ninds[0]; else mn = 0.0; + if (ts.ninds[0] > 1) sd = ts.ssqGeneticFitness[0] / (double)ts.ninds[0] - mn * mn; else sd = 0.0; + if (sd > 0.0) sd = sqrt(sd); else sd = 0.0; + outtraitsrows << "\t" << mn << "\t" << sd; + } + } outtraitsrows << endl; } -// Open trait rows file and write header record -bool Community::outTraitsRowsHeaders(Species* pSpecies, int landNr) { - - if (landNr == -999) { // close file - if (outtraitsrows.is_open()) outtraitsrows.close(); - outtraitsrows.clear(); - return true; - } +// Close trait rows file +bool Community::outTraitsRowsFinishLandscape() { + if (outtraitsrows.is_open()) outtraitsrows.close(); + outtraitsrows.clear(); + return true; +} +// Open trait rows file and write header record +bool Community::outTraitsRowsStartLandscape(Species* pSpecies, int landNr) { string name; - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); simParams sim = paramsSim->getSim(); string DirOut = paramsSim->getDir(2); if (sim.batchMode) { name = DirOut - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) + "_Land" + Int2Str(landNr) + "_TraitsXrow.txt"; + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + to_string(landNr) + "_TraitsXrow.txt"; } else { - name = DirOut + "Sim" + Int2Str(sim.simulation) + "_TraitsXrow.txt"; + name = DirOut + "Sim" + to_string(sim.simulation) + "_TraitsXrow.txt"; } outtraitsrows.open(name.c_str()); @@ -1483,7 +1527,7 @@ bool Community::outTraitsRowsHeaders(Species* pSpecies, int landNr) { } } if (trfr.indVar) { - if (trfr.moveModel) { + if (trfr.usesMovtProc) { if (trfr.moveType == 2) { outtraitsrows << "\tmeanStepLength\tstdStepLength\tmeanRho\tstdRho"; } @@ -1505,10 +1549,26 @@ bool Community::outTraitsRowsHeaders(Species* pSpecies, int landNr) { } if (sett.indVar) { + // if (sett.sexDep) { + // outtraitsrows << "\tF_meanS0\tF_stdS0\tM_meanS0\tM_stdS0"; + // outtraitsrows << "\tF_meanAlphaS\tF_stdAlphaS\tM_meanAlphaS\tM_stdAlphaS"; + // outtraitsrows << "\tF_meanBetaS\tF_stdBetaS\tM_meanBetaS\tM_stdBetaS"; + // } + // else { outtraitsrows << "\tmeanS0\tstdS0"; outtraitsrows << "\tmeanAlphaS\tstdAlphaS"; outtraitsrows << "\tmeanBetaS\tstdBetaS"; + // } } + + if (pSpecies->getNbGenLoadTraits() > 0) { + if (gMaxNbSexes > 1) { + outtraitsrows << "\tF_meanProbViable\tF_stdProbViable\tM_meanProbViable\tM_stdProbViable"; + } + else + outtraitsrows << "\tmeanProbViable\tstdProbViable"; + } + outtraitsrows << endl; return outtraitsrows.is_open(); @@ -1516,47 +1576,535 @@ bool Community::outTraitsRowsHeaders(Species* pSpecies, int landNr) { } #if RS_RCPP && !R_CMD -Rcpp::IntegerMatrix Community::addYearToPopList(int rep, int yr) { // TODO: define new simparams to control start and interval of output - +Rcpp::IntegerMatrix Community::addYearToPopList(int rep, int yr, PopOutType type, int stage) { // TODO: define new simparams to control start and interval of output + /* Rcpp::Rcout << "Calling addYearToPopList: " + << "rep=" << rep + << " yr=" << yr + << " type=" << (int)type + << " stage=" << stage << endl; +*/ landParams ppLand = pLandscape->getLandParams(); Rcpp::IntegerMatrix pop_map_year(ppLand.dimY, ppLand.dimX); - intptr patch = 0; Patch* pPatch = 0; - intptr subcomm = 0; - SubCommunity* pSubComm = 0; + SubCommunity* pSubComm = nullptr; popStats pop; pop.nInds = pop.nAdults = pop.nNonJuvs = 0; for (int y = 0; y < ppLand.dimY; y++) { for (int x = 0; x < ppLand.dimX; x++) { - Cell* pCell = pLandscape->findCell(x, y); + Cell* pCell = pLandscape->findCell(x, y); //if (pLandscape->cells[y][x] == 0) { if (pCell == 0) { // no-data cell pop_map_year(ppLand.dimY - 1 - y, x) = NA_INTEGER; } else { - patch = pCell->getPatch(); - if (patch == 0) { // matrix cell + pPatch = pCell->getPatch(); + if (pPatch == nullptr) { // matrix cell pop_map_year(ppLand.dimY - 1 - y, x) = 0; } else { - pPatch = (Patch*)patch; - subcomm = pPatch->getSubComm(); - if (subcomm == 0) { // check if sub-community exists + pSubComm = pPatch->getSubComm(); + if (pSubComm == nullptr) { // check if sub-community exists pop_map_year(ppLand.dimY - 1 - y, x) = 0; } else { - pSubComm = (SubCommunity*)subcomm; pop = pSubComm->getPopStats(); - pop_map_year(ppLand.dimY - 1 - y, x) = pop.nInds; // use indices like this because matrix gets transposed upon casting it into a raster on R-level + + switch (type) { + case PopOutType::NInd: + pop_map_year(ppLand.dimY - 1 - y, x) = pop.nInds; + break; + + case PopOutType::Stage: + pop_map_year(ppLand.dimY - 1 - y, x) = pSubComm->getNbInds(stage); // check if function is correct? + break; + + case PopOutType::Juvs: + pop_map_year(ppLand.dimY - 1 - y, x) = pSubComm->getNbInds(0); + break; + } + // pop_map_year(ppLand.dimY - 1 - y, x) = pop.nInds; // use indices like this because matrix gets transposed upon casting it into a raster on R-level + //pop_map_year(ppLand.dimY-1-y,x) = pop.nAdults; } } } } } + //list_outPop.push_back(pop_map_year, "rep" + std::to_string(rep) + "_year" + std::to_string(yr)); + return pop_map_year; +} + +// write a similar function for patch-based models; +// Instead of a spatial x,y raster, the output should also be a Rcpp::IntegerMatrix with PatchID and population size (or stage-specific population size) for each patch. +// The number of columns is determined by what the user specified in ReturnStages: +// As default its 2 columns: 1st column is the patch ID, 2nd column is the total abundance. +// Depending on the user specification the columns 3 to maximal (number of stages + 2) +// can contain the abundance of each stage (e.g. column 3 is abundance of juveniles (stage 0), column 4 is abundance of stage 1 etc). +// But only selected stages are included, so if the user only wants to output juveniles and adults, +// then column 3 is abundance of juveniles (stage 0) and column 4 is abundance of adults (stage 1), and no other stages are included in the output. +// After the runtime, the user can create a spatial raster in R by joining it with the patch coordinates. +// be aware: the output is then not a spatial raster, but a table +Rcpp::IntegerMatrix Community::addYearToPopListPatchBased(int rep, int yr, Rcpp::LogicalVector stages) { + /* Rcpp::Rcout << "Calling addYearToPopListPatchBased: " + << "rep=" << rep + << " yr=" << yr << endl;*/ + int nrows=pLandscape->getPatchNbs().size(); + int ncols = 2; // for patchID + total abundance + std::vector stageIndices; + + for (int i = 0; i < stages.length(); i++) { + if (stages[i]) { + stageIndices.push_back(i); + } + } + ncols += stageIndices.size(); + + + Rcpp::IntegerMatrix pop_map_year(nrows, ncols); // 2 columns: 1st column is the patch ID, 2nd column is the total abundance (or stage-specific abundance depending on user specification) + Patch* pPatch = nullptr; + SubCommunity* pSubComm = nullptr; + int currentRow = 0; + + for (auto patchId : pLandscape->getPatchNbs()) { + // pPatch = pLandscape->findPatch(patchId); + // if (pPatch == nullptr) { // check if patch exists + // continue; // skip to next patch + // } else{ + // pSubComm = pPatch->getSubComm(); + // if (pSubComm == nullptr) { // check if sub-community exists + // pop = pSubComm->getPopStats(); + // pop_map_year(patchId, 0) = patchId; // 1st column is patch ID + // pop_map_year(patchId, 1) = 0; // 2nd column is total abundance + // // additional columns for stage-specific abundances depending on user specification + // for(int i = 0; i < stages.length(); i++) { + // int ncol = 0; + // if(stages[i]) { + // pop_map_year(patchId, 2 + ncol) = 0; // all following columns are stage specific columns depending on the users specifications + // ncol++; + // } + // } + // } else { + // pop = pSubComm->getPopStats(); + // pop_map_year(patchId, 0) = patchId; // 1st column is patch ID + // pop_map_year(patchId, 1) = pop.nInds; // 2nd column is total abundance + // // additional columns for stage-specific abundances depending on user specification + // for (int i = 0; i < stages.length(); i++) { + // int ncol = 0; + // if(stages[i]) { + // pop_map_year(patchId, 2 + ncol) = pSubComm->getNbInds(stages[i]); // all following columns are stage specific columns depending on the users specifications + // ncol++; + // } + // } + // } + // } + pop_map_year(currentRow, 0) = patchId; // 1st column: patch ID + // loop over ncols to fill default to 0 + for(int i = 1; i < ncols; i++) { + pop_map_year(currentRow, i) = 0; // 2nd column: default total abundance + } + + pPatch = pLandscape->findPatch(patchId); + if (pPatch != nullptr) { // Valid patch + pSubComm = pPatch->getSubComm(); + if (pSubComm != nullptr) { // Valid sub-community + popStats pop = pSubComm->getPopStats(); + pop_map_year(currentRow, 1) = pop.nInds; // Actual total abundance + + for (int idx = 0; idx < stageIndices.size(); ++idx) { + int stage = stageIndices[idx]; + pop_map_year(currentRow, 2 + idx) = pSubComm->getNbInds(stage); + } + } + } + currentRow++; + } + return pop_map_year; + // } + // return pop_map_year; } + #endif +bool Community::openOutGenesFile(const bool& isDiploid, const int landNr, const int rep) +{ + if (landNr == -999) { // close the file + if (ofsGenes.is_open()) { + ofsGenes.close(); + ofsGenes.clear(); + } + return true; + } + string name; + simParams sim = paramsSim->getSim(); + + if (sim.batchMode) { + name = paramsSim->getDir(2) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + + to_string(landNr) + "_Rep" + to_string(rep) + + "_geneValues.txt"; + } + else { + name = paramsSim->getDir(2) + + "Sim" + to_string(sim.simulation) + "_Land" + + to_string(landNr) + "_Rep" + to_string(rep) + + "_geneValues.txt"; + } + + ofsGenes.open(name.c_str()); + ofsGenes << "Year\tGeneration\tIndID\ttraitType\tlocusPosition" + << "\talleleValueA\tdomCoefA"; + if (isDiploid) ofsGenes << "\talleleValueB\tdomCoefB"; + ofsGenes << endl; + + return ofsGenes.is_open(); +} + +void Community::outputGeneValues(const int& year, const int& gen, Species* pSpecies) { + if (!ofsGenes.is_open()) + throw runtime_error("Could not open output gene values file."); + + const set patchList = pSpecies->getSamplePatches(); + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + if (patch == 0) { + throw runtime_error("Sampled patch does not exist"); + } + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + pPop->outputGeneValues(ofsGenes, year, gen); + } + } +} + +// ---------------------------------------------------------------------------------------- +// Sample individuals from sample patches +// ---------------------------------------------------------------------------------------- + +void Community::sampleIndividuals(Species* pSpecies) { + + const set patchList = pSpecies->getSamplePatches(); + string nbIndsToSample = pSpecies->getNIndsToSample(); + const set stagesToSampleFrom = pSpecies->getStagesToSample(); + + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + if (patch == 0) { + throw runtime_error("Can't sample individuals: patch" + to_string(patchId) + "doesn't exist."); + } + auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + pPop->sampleIndsWithoutReplacement(nbIndsToSample, stagesToSampleFrom); + } + } +} + +// ---------------------------------------------------------------------------------------- +// Open population level Fstat output file +// ---------------------------------------------------------------------------------------- + +bool Community::openNeutralOutputFile(Species* pSpecies, int landNr) +{ + if (landNr == -999) { // close the file + if (outwcfstat.is_open()) outwcfstat.close(); + outwcfstat.clear(); + return true; + } + + string name; + simParams sim = paramsSim->getSim(); + + if (sim.batchMode) { + name = paramsSim->getDir(2) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + + to_string(landNr) + + "_neutralGenetics.txt"; + } + else { + name = paramsSim->getDir(2) + "Sim" + to_string(sim.simulation) + "_neutralGenetics.txt"; + } + outwcfstat.open(name.c_str()); + outwcfstat << "Rep\tYear\tRepSeason\tnExtantPatches\tnIndividuals\tFstWC\tFisWC\tFitWC\tmeanAllelePerLocus\tmeanAllelePerLocusPatches\tmeanFixedLoci\tmeanFixedLociPatches\tmeanObHeterozygosity"; + outwcfstat << endl; + + return outwcfstat.is_open(); +} + +// ---------------------------------------------------------------------------------------- +// open per locus WC fstat using MS approach, this will output MS calculated FIS, FIT, FST +// in general population neutral genetics output file +// ---------------------------------------------------------------------------------------- + +bool Community::openPerLocusFstFile(Species* pSpecies, Landscape* pLandscape, const int landNr, const int rep) +{ + set patchList; + string samplingOpt = paramsSim->getSim().patchSamplingOption; + if (samplingOpt == "list" + || samplingOpt == "random" + || (samplingOpt == "all" && !pLandscape->getLandParams().dynamic) + ) // list of patches always the same + patchList = pSpecies->getSamplePatches(); + else { // random_occupied or all with dynamic landscape + // then sampled patches may change every year, + // so produce an entry for all patches + patchList = pLandscape->getPatchNbs(); + } + + if (landNr == -999) { // close the file + if (outperlocusfstat.is_open()) outperlocusfstat.close(); + outperlocusfstat.clear(); + return true; + } + + string name; + simParams sim = paramsSim->getSim(); + + if (sim.batchMode) { + name = paramsSim->getDir(2) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + + to_string(landNr) + "_Rep" + + to_string(rep) + + "_perLocusNeutralGenetics.txt"; + } + else { + name = paramsSim->getDir(2) + "Sim" + to_string(sim.simulation) + "_Rep" + to_string(rep) + "_perLocusNeutralGenetics.txt"; + } + + outperlocusfstat.open(name.c_str()); + outperlocusfstat << "Year\tRepSeason\tLocus\tFst\tFis\tFit\tHet"; + for (int patchId : patchList) { + outperlocusfstat << "\tpatch_" + to_string(patchId) + "_Het"; + } + outperlocusfstat << endl; + + return outperlocusfstat.is_open(); +} + +// ---------------------------------------------------------------------------------------- +// open pairwise fst file +// ---------------------------------------------------------------------------------------- + +bool Community::openPairwiseFstFile(Species* pSpecies, Landscape* pLandscape, const int landNr, const int rep) { + + const set patchList = pSpecies->getSamplePatches(); + + if (landNr == -999) { // close the file + if (outpairwisefst.is_open()) outpairwisefst.close(); + outpairwisefst.clear(); + return true; + } + + string name; + simParams sim = paramsSim->getSim(); + + if (sim.batchMode) { + name = paramsSim->getDir(2) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + + to_string(landNr) + "_Rep" + + to_string(rep) + + "_pairwisePatchNeutralGenetics.txt"; + } + else { + name = paramsSim->getDir(2) + "Sim" + to_string(sim.simulation) + "_Rep" + to_string(rep) + "_pairwisePatchNeutralGenetics.txt"; + } + outpairwisefst.open(name.c_str()); + outpairwisefst << "Year\tRepSeason\tpatchA\tpatchA_x\tpatchA_y\tpatchB\tpatchB_x\tpatchB_y\tFst"; + outpairwisefst << endl; + + return outpairwisefst.is_open(); +} + +// ---------------------------------------------------------------------------------------- +// Write population level FST results file +// ---------------------------------------------------------------------------------------- + +void Community::writeNeutralOutputFile(int rep, int yr, int gen) { + + outwcfstat << rep << "\t" << yr << "\t" << gen << "\t"; + outwcfstat << pNeutralStatistics->getNbPopulatedSampledPatches() + << "\t" << pNeutralStatistics->getTotalNbSampledInds() << "\t"; + + + outwcfstat << pNeutralStatistics->getFstWC() << "\t" + << pNeutralStatistics->getFisWC() << "\t" + << pNeutralStatistics->getFitWC() << "\t"; + + + + outwcfstat << pNeutralStatistics->getMeanNbAllPerLocus() << "\t" + << pNeutralStatistics->getMeanNbAllPerLocusPerPatch() << "\t" + << pNeutralStatistics->getTotalFixdAlleles() << "\t" + << pNeutralStatistics->getMeanFixdAllelesPerPatch() << "\t" + << pNeutralStatistics->getHo(); + + outwcfstat << endl; +} + +// ---------------------------------------------------------------------------------------- +// Write per locus FST results file +// ---------------------------------------------------------------------------------------- + +void Community::writePerLocusFstatFile(Species* pSpecies, const int yr, const int gen, const int nLoci, set const& patchList) +{ + string samplingOpt = paramsSim->getSim().patchSamplingOption; + bool samplingFixed = samplingOpt == "list" + || samplingOpt == "random" + || (samplingOpt == "all" && !pLandscape->getLandParams().dynamic); + + const set positions = pSpecies->getSpTrait(NEUTRAL)->getGenePositions(); + + int thisLocus = 0; + for (int position : positions) { + + outperlocusfstat << yr << "\t" + << gen << "\t" + << position << "\t"; + outperlocusfstat << pNeutralStatistics->getPerLocusFst(thisLocus) << "\t" + << pNeutralStatistics->getPerLocusFis(thisLocus) << "\t" + << pNeutralStatistics->getPerLocusFit(thisLocus) << "\t" + << pNeutralStatistics->getPerLocusHo(thisLocus); + + if (samplingFixed) { // then safe to output sampled patches in order + for (int patchId : patchList) { + float het = getPatchHet(pSpecies, patchId, thisLocus); + if (het < 0) // patch empty + outperlocusfstat << "\t" << "NA"; + else outperlocusfstat << "\t" << het; + } + } + else { // sampling may change between generations, must produce output for all patches in Landscape + for (auto patchId : pLandscape->getPatchNbs()) { + if (patchList.find(patchId) != patchList.end()) { + float het = getPatchHet(pSpecies, patchId, thisLocus); + if (het < 0) // patch empty + outperlocusfstat << "\t" << "NA"; + else outperlocusfstat << "\t" << het; + } + else { // patch not sampled + outperlocusfstat << "\t" << "NA"; + } + } + } + ++thisLocus; + outperlocusfstat << endl; + } +} + +// Calculate the observed heterozygosity (Ho) for a patch +// = number of heterozygous individuals at this locus / nb individuals in patch +float Community::getPatchHet(Species* pSpecies, int patchId, int whichLocus) const { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + int nAlleles = pSpecies->getSpTrait(NEUTRAL)->getNbNeutralAlleles(); + int popSize = 0; + float het = 0; + if (pPop != 0) { + popSize = pPop->sampleSize(); + if (popSize == 0) return -1.0; + else { + for (int a = 0; a < nAlleles; ++a) { + het += static_cast(pPop->getHeteroTally(whichLocus, a)); + } + het /= popSize; + return het; + } + } + else return -1.0; +} + + +// ---------------------------------------------------------------------------------------- +// Write pairwise FST results file +// ---------------------------------------------------------------------------------------- +void Community::writePairwiseFstFile(Species* pSpecies, const int yr, const int gen, set const& patchList) { + + const int nPatches = static_cast(patchList.size()); + // Convert set to vector for index-based access + vector patchVect; + copy(patchList.begin(), patchList.end(), back_inserter(patchVect)); + + for (int i = 0; i < nPatches; ++i) + { + const auto patchA = pLandscape->findPatch(patchVect[i]); + + for (int j = i; j < nPatches; ++j) + { + + const auto patchB = pLandscape->findPatch(patchVect[j]); + + outpairwisefst << yr << "\t" + << gen << "\t" + << patchVect[i] << "\t" + << patchA->getSubComm()->getLocn().x << "\t" + << patchA->getSubComm()->getLocn().y << "\t" + << patchVect[j] << "\t" + << patchB->getSubComm()->getLocn().x << "\t" + << patchB->getSubComm()->getLocn().y << "\t" + << pNeutralStatistics->getPairwiseFst(i, j) + << "\n"; + } + } + + +} + + +// ---------------------------------------------------------------------------------------- +// Output and calculate neutral statistics +// ---------------------------------------------------------------------------------------- + + +void Community::calculateNeutralGenetics(Species* pSpecies, int rep, int yr, int gen, bool outPairwiseFst, int outputPairwiseFstStart, int outputPairwiseFstInterval, + bool outputGlobalFst, int outputGlobalFstStart, int outputGlobalFstInterval, bool outputPerLocusFst) { + + const int maxNbNeutralAlleles = pSpecies->getSpTrait(NEUTRAL)->getNbNeutralAlleles(); + const int nLoci = (int)pSpecies->getNPositionsForTrait(NEUTRAL); + const set patchList = pSpecies->getSamplePatches(); + int nInds = 0, nbPops = 0; + + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + if (patch == 0) { + throw runtime_error("Sampled patch does not exist"); + } + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { // empty patches do not contribute + nInds += pPop->sampleSize(); + nbPops++; + } + } + + if (pNeutralStatistics == 0) + pNeutralStatistics = make_unique(patchList.size(), nLoci); + + pNeutralStatistics->updateAllNeutralTables(pSpecies, pLandscape, patchList); + pNeutralStatistics->calculateHo(patchList, nInds, nLoci, pSpecies, pLandscape); + pNeutralStatistics->calculatePerLocusHo(patchList, nInds, nLoci, pSpecies, pLandscape); + pNeutralStatistics->calcAllelicDiversityMetrics(patchList, nInds, pSpecies, pLandscape); + + if (outPairwiseFst) { + pNeutralStatistics->calculatePairwiseFst(patchList, nLoci, maxNbNeutralAlleles, pSpecies, pLandscape); + + if (yr >= outputPairwiseFstStart && yr % outputPairwiseFstInterval == 0) { + writePairwiseFstFile(pSpecies, yr, gen, patchList); + } + } + if (outputGlobalFst) { + pNeutralStatistics->calculateFstatWC(patchList, nInds, nLoci, maxNbNeutralAlleles, pSpecies, pLandscape, false); + + if (yr >= outputGlobalFstStart && yr % outputGlobalFstInterval == 0) { + writeNeutralOutputFile(rep, yr, gen); + if (outputPerLocusFst) + writePerLocusFstatFile(pSpecies, yr, gen, nLoci, patchList); + } + } + +} + + //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- diff --git a/Community.h b/Community.h index c55b333..f102fd1 100644 --- a/Community.h +++ b/Community.h @@ -1,25 +1,25 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - + + /*------------------------------------------------------------------------------ RangeShifter v2.0 Community @@ -35,9 +35,9 @@ Optionally, the Community maintains a record of the occupancy of suitable cells or patches during the course of simulation of multiple replicates. For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. + Bocedi G., Palmer S.C.F., Pe’er G., Heikkinen R.K., Matsinos Y.G., Watts K. and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. + eco-evolutionary dynamics and species’ responses to environmental changes. Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 Authors: Greta Bocedi & Steve Palmer, University of Aberdeen @@ -51,6 +51,8 @@ Last updated: 25 June 2021 by Anne-Kathleen Malchow #include #include +#include +#include using namespace std; #include "SubCommunity.h" @@ -58,6 +60,7 @@ using namespace std; #include "Patch.h" #include "Cell.h" #include "Species.h" +#include "NeutralStatsManager.h" //--------------------------------------------------------------------------- struct commStats { @@ -65,6 +68,14 @@ int ninds,nnonjuvs,suitable,occupied; int minX,maxX,minY,maxY; }; +#if RS_RCPP// For raster output only: which type of population output should be stored? +enum class PopOutType { + NInd, // total abundance + Stage, // specific stages + Juvs // juvenile stage +}; +#endif + class Community { public: @@ -95,15 +106,14 @@ class Community { ); #endif // SEASONAL || RS_RCPP - void survival( - short, // part: 0 = determine survival & development, - // 1 = apply survival changes to the population + void survival0( // Determine survival & development short, // option0: 0 = stage 0 (juveniles) only ) // 1 = all stages ) used by part 0 only // 2 = stage 1 and above (all non-juvs) ) short // option1: 0 - development only (when survival is annual) // 1 - development and survival ); + void survival1(); // Apply survival changes to the population void ageIncrement(void); int totalInds(void); Population* findPop( // Find the population of a given species in a given patch @@ -123,9 +133,10 @@ class Community { int // no. of rows (as above) ); - bool outRangeHeaders( // Open range file and write header record + bool outRangeFinishLandscape(); // Close range file + bool outRangeStartLandscape( // Open range file and write header record Species*, // pointer to Species - int // Landscape number (-999 to close the file) + int // Landscape number ); void outRange( // Write record to range file Species*, // pointer to Species @@ -133,9 +144,9 @@ class Community { int, // year int // generation ); - bool outPopHeaders( // Open population file and write header record - Species*, // pointer to Species - int // option: -999 to close the file + bool outPopFinishLandscape(); // Close population file + bool outPopStartLandscape( // Open population file and write header record + Species* // pointer to Species ); void outPop( // Write records to population file int, // replicate @@ -143,46 +154,33 @@ class Community { int // generation ); - void outInds( // Write records to individuals file + void outIndsFinishReplicate(); // Close individuals file + void outIndsStartReplicate( // Open individuals file and write header record int, // replicate - int, // year - int, // generation - int // Landscape number (>= 0 to open the file, -999 to close the file - // -1 to write data records) + int // Landscape number ); - void outGenetics( // Write records to genetics file + void outIndividuals( // Write records to individuals file int, // replicate int, // year - int, // generation - int // Landscape number (>= 0 to open the file, -999 to close the file - // -1 to write data records) + int // generation ); + // Close occupancy file + bool outOccupancyFinishLandscape(); // Open occupancy file, write header record and set up occupancy array - bool outOccupancyHeaders( - int // option: -999 to close the file - ); + bool outOccupancyStartLandscape(); void outOccupancy(void); - void outOccSuit( - bool // TRUE if occupancy graph is to be viewed on screen - ); - void viewOccSuit( // Update the occupancy graph on the screen - // NULL for the batch version - int, // year - double, // mean occupancy - double // standard error of occupancy - ); - bool outTraitsHeaders( // Open traits file and write header record + void outOccSuit(); + bool outTraitsFinishLandscape(); // Close traits file + bool outTraitsStartLandscape( // Open traits file and write header record Species*, // pointer to Species - int // Landscape number (-999 to close the file) + int // Landscape number ); - bool outTraitsRowsHeaders( // Open trait rows file and write header record + bool outTraitsRowsFinishLandscape(); // Close trait rows file + bool outTraitsRowsStartLandscape( // Open trait rows file and write header record Species*, // pointer to Species - int // Landscape number (-999 to close the file) + int // Landscape number ); void outTraits( // Write records to traits file - traitCanvas,// pointers to canvases for drawing variable traits - // see SubCommunity.h - // in the batch version, these are replaced by integers set to zero Species*, // pointer to Species int, // replicate int, // year @@ -196,23 +194,42 @@ class Community { int, // row number (Y cell co-ordinate) traitsums // structure holding sums of trait genes for dispersal (see Population.h) ); - void draw( // Draw the Community on the landscape map and optionally save the map - // NULL for the batch version - int, // replicate - int, // year - int, // generation - int // Landscape number - ); #if RS_RCPP && !R_CMD - Rcpp::IntegerMatrix addYearToPopList(int,int); + Rcpp::IntegerMatrix addYearToPopList(int,int,PopOutType,int); + + Rcpp::IntegerMatrix addYearToPopListPatchBased(int,int,Rcpp::LogicalVector); #endif + //sample individuals for genetics (or could be used for anything) + void sampleIndividuals(Species* pSpecies); + + bool openOutGenesFile(const bool& isDiploid, const int landNr, const int rep); + void outputGeneValues(const int& year, const int& gen, Species* pSpecies); + + //control neutral stat output + + void calculateNeutralGenetics(Species* pSpecies, int rep, int yr, int gen, bool outPairwiseFst, int outputPairwiseFstStart, int outputPairwiseFstInterval, + bool outputGlobalFst, int outputGlobalFstStart, int outputGlobalFstInterval, bool outputPerLocusFst); + + + //file openers + bool openNeutralOutputFile(Species* pSpecies, const int landNr); + bool openPerLocusFstFile(Species* pSpecies, Landscape* pLandscape, const int landNr, const int rep); + bool openPairwiseFstFile(Species* pSpecies, Landscape* pLandscape, const int landNr, const int rep); + + //file writers + void writeNeutralOutputFile(int rep, int yr, int gen); + void writePerLocusFstatFile(Species* pSpecies, const int yr, const int gen, const int nLoci, set const& patchList); + void writePairwiseFstFile(Species* pSpecies, const int yr, const int gen, set const& patchList); + float getPatchHet(Species* pSpecies, int patchId, int whichLocus) const; private: Landscape *pLandscape; int indIx; // index used to apply initial individuals float **occSuit; // occupancy of suitable cells / patches std::vector subComms; + //below won't work for multispecies + unique_ptr pNeutralStatistics; }; extern paramSim *paramsSim; diff --git a/DispersalTrait.cpp b/DispersalTrait.cpp new file mode 100644 index 0000000..f0a805d --- /dev/null +++ b/DispersalTrait.cpp @@ -0,0 +1,489 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + +#include "DispersalTrait.h" + +// ---------------------------------------------------------------------------------------- +// Initialisation constructor +// Called when initialising community +// Sets up initial values, and immutable attributes (distributions and parameters) +// that are defined at the species-level +// ---------------------------------------------------------------------------------------- +DispersalTrait::DispersalTrait(SpeciesTrait* P) +{ + pSpeciesTrait = P; + ExpressionType expressionType = pSpeciesTrait->getExpressionType(); + + if (!pSpeciesTrait->isInherited()) // there is a trait for individual variation but this isn't inherited variation it's sampled from initial distribution + _inherit_func_ptr = &DispersalTrait::reInitialiseGenes; + else { + _inherit_func_ptr = (pSpeciesTrait->getPloidy() == 1) ? &DispersalTrait::inheritHaploid : &DispersalTrait::inheritDiploid; //this could be changed if we wanted some alternative form of inheritance + + DistributionType mutationDistribution = pSpeciesTrait->getMutationDistribution(); + map mutationParameters = pSpeciesTrait->getMutationParameters(); + + // Set mutation parameters + switch (mutationDistribution) { + case UNIFORM: + { + if (mutationParameters.count(MAX) != 1) + throw logic_error("mutation uniform distribution parameter must contain max value (e.g. max= ) \n"); + if (mutationParameters.count(MIN) != 1) + throw logic_error("mutation uniform distribution parameter must contain min value (e.g. min= ) \n"); + _mutate_func_ptr = &DispersalTrait::mutateUniform; + break; + } + case NORMAL: + { + if (mutationParameters.count(MEAN) != 1) + throw logic_error("mutation distribution set to normal so parameters must contain mean value (e.g. mean= ) \n"); + if (mutationParameters.count(SD) != 1) + throw logic_error("mutation distribution set to normal so parameters must contain sdev value (e.g. sdev= ) \n"); + _mutate_func_ptr = &DispersalTrait::mutateNormal; + break; + } + default: + { + throw logic_error("wrong parameter value for mutation model, must be uniform/normal \n"); //unless want to add gamma or negative exp + break; + } + } + } + + // Set initialisation parameters + DistributionType initialDistribution = pSpeciesTrait->getInitialDistribution(); + map initialParameters = pSpeciesTrait->getInitialParameters(); + switch (initialDistribution) { + case UNIFORM: + { + if (initialParameters.count(MAX) != 1) + throw logic_error("initial uniform distribution parameter must contain max value (e.g. max= ) \n"); + if (initialParameters.count(MIN) != 1) + throw logic_error("initial uniform distribution parameter must contain min value (e.g. min= ) \n"); + float maxD = initialParameters.find(MAX)->second; + float minD = initialParameters.find(MIN)->second; + initialiseUniform(minD, maxD); + break; + } + case NORMAL: + { + if (initialParameters.count(MEAN) != 1) + throw logic_error("initial normal distribution parameter must contain mean value (e.g. mean= ) \n"); + if (initialParameters.count(SD) != 1) + throw logic_error("initial normal distribution parameter must contain sdev value (e.g. sdev= ) \n"); + float mean = initialParameters.find(MEAN)->second; + float sd = initialParameters.find(SD)->second; + initialiseNormal(mean, sd); + break; + } + default: + { + throw logic_error("wrong parameter value for parameter \"initialisation of dispersal traits\", must be uniform/normal \n"); + break; + } + } + + // Set expression mode parameters + switch (expressionType) { + case AVERAGE: + { + _express_func_ptr = &DispersalTrait::expressAverage; + break; + } + case ADDITIVE: + { + _express_func_ptr = &DispersalTrait::expressAdditive; + break; + } + default: + { + throw logic_error("wrong parameter value for parameter \"expression of dispersal trait\", must be average/additive \n"); + break; + } + } +} + +// ---------------------------------------------------------------------------------------- +// Inheritance constructor +// Copies immutable features from a parent trait +// Only called via clone() +// ---------------------------------------------------------------------------------------- +DispersalTrait::DispersalTrait(const DispersalTrait& T) : + pSpeciesTrait(T.pSpeciesTrait), + _mutate_func_ptr(T._mutate_func_ptr), + _inherit_func_ptr(T._inherit_func_ptr), + _express_func_ptr(T._express_func_ptr) {} + +// ---------------------------------------------------------------------------------------- +// Sample and apply mutations from a uniform distribution +// +// Mutations drawn only for existing positions, +// that is no new genes are created during simulation +// ---------------------------------------------------------------------------------------- +void DispersalTrait::mutateUniform() +{ + const int positionsSize = pSpeciesTrait->getPositionsSize(); + const auto& genePositions = pSpeciesTrait->getGenePositions(); + const short ploidy = pSpeciesTrait->getPloidy(); + const float mutationRate = pSpeciesTrait->getMutationRate(); + float newAlleleVal; + + auto rng = pRandom->getRNG(); + + map mutationParameters = pSpeciesTrait->getMutationParameters(); + float maxD = mutationParameters.find(MAX)->second; + float minD = mutationParameters.find(MIN)->second; + + for (int p = 0; p < ploidy; p++) { + + unsigned int NbMut = pRandom->Binomial(positionsSize, mutationRate); + + if (NbMut > 0) { + vector mutationPositions; + sample(genePositions.begin(), genePositions.end(), std::back_inserter(mutationPositions), + NbMut, rng); + + for (int m : mutationPositions) { + auto it = genes.find(m); + if (it == genes.end()) + throw runtime_error("Locus sampled for mutation doesn't exist."); + float currentAlleleVal = it->second[p].get()->getAlleleValue();//current + newAlleleVal = pRandom->FRandom(minD, maxD) + currentAlleleVal; + it->second[p] = make_shared(newAlleleVal, dispDominanceFactor); + } + } + } +} + +// ---------------------------------------------------------------------------------------- +// Sample and apply mutations from a normal distribution +// Mutations drawn only for existing positions, +// that is no new genes are created during simulation +// ---------------------------------------------------------------------------------------- +void DispersalTrait::mutateNormal() +{ + const int positionsSize = pSpeciesTrait->getPositionsSize(); + const auto& genePositions = pSpeciesTrait->getGenePositions(); + const short ploidy = pSpeciesTrait->getPloidy(); + const float mutationRate = pSpeciesTrait->getMutationRate(); + + auto rng = pRandom->getRNG(); + + const map mutationParameters = pSpeciesTrait->getMutationParameters(); + const float mean = mutationParameters.find(MEAN)->second; + const float sd = mutationParameters.find(SD)->second; + float newAlleleVal; + + for (int p = 0; p < ploidy; p++) { + + unsigned int NbMut = pRandom->Binomial(positionsSize, mutationRate); + + if (NbMut > 0) { + vector mutationPositions; + sample(genePositions.begin(), genePositions.end(), std::back_inserter(mutationPositions), + NbMut, rng); + + for (int m : mutationPositions) { + auto it = genes.find(m); + if (it == genes.end()) + throw runtime_error("Locus sampled for mutation doesn't exist."); + float currentAlleleVal = it->second[p].get()->getAlleleValue(); //current + newAlleleVal = pRandom->Normal(mean, sd) + currentAlleleVal; + it->second[p] = make_shared(newAlleleVal, dispDominanceFactor); + } + } + } +} + +// ---------------------------------------------------------------------------------------- +// Wrapper to inheritance function +// ---------------------------------------------------------------------------------------- +void DispersalTrait::inheritGenes(const bool& fromMother, QuantitativeTrait* parentTrait, set const& recomPositions, int startingChromosome) +{ + auto parentCast = dynamic_cast(parentTrait); // must convert QuantitativeTrait to DispersalTrait + const auto& parent_seq = parentCast->getGenes(); + (this->*_inherit_func_ptr)(fromMother, parent_seq, recomPositions, startingChromosome); +} + +// ---------------------------------------------------------------------------------------- +// Inheritance for diploid, sexual species +// Called once for each parent. +// Pass the correct parental strand, resolving crossing-overs +// after each recombinant site e.g. if parent genotype is +// 0000 +// 1111 +// and position 2 is selected to recombine, then offspring inherits +// 0001 +// Assumes mother genes are inherited first. +// ---------------------------------------------------------------------------------------- +void DispersalTrait::inheritDiploid(const bool& fromMother, map>> const& parentGenes, set const& recomPositions, int parentChromosome) { + + const int lastPosition = parentGenes.rbegin()->first; + auto recomIt = recomPositions.lower_bound(parentGenes.begin()->first); + // If no recombination sites, only breakpoint is last position + // i.e., no recombination occurs + int nextBreakpoint = recomIt == recomPositions.end() ? lastPosition : *recomIt; + + // Is the first parent gene position already recombinant? + auto distance = std::distance(recomPositions.begin(), recomIt); + if (distance % 2 != 0) // odd positions = switch, even = switch back + parentChromosome = 1 - parentChromosome; //switch chromosome + + for (auto const& [locus, allelePair] : parentGenes) { + + // Switch chromosome if locus is past recombination site + while (locus > nextBreakpoint) { + parentChromosome = 1 - parentChromosome; + std::advance(recomIt, 1); // go to next recombination site + nextBreakpoint = recomIt == recomPositions.end() ? lastPosition : *recomIt; + } + + if (locus <= nextBreakpoint) { + auto& parentAllele = allelePair[parentChromosome]; + auto itGene = genes.find(locus); + if (itGene == genes.end()) { + // locus does not exist yet, create and initialise it + if (!fromMother) throw runtime_error("Father-inherited locus does not exist."); + vector> newAllelePair(2); + newAllelePair[sex_t::FEM] = parentAllele; + genes.insert(make_pair(locus, newAllelePair)); + } + else { // father, locus already exists + if (fromMother) throw runtime_error("Mother-inherited locus already exists."); + itGene->second[sex_t::MAL] = parentAllele; + } + } + } +} + +// ---------------------------------------------------------------------------------------- +// Inheritance for haploid, asexual species +// Simply pass down parent genes +// Arguments are still needed to match overloaded function in base class +// ---------------------------------------------------------------------------------------- +void DispersalTrait::inheritHaploid(const bool& fromMother, map>> const& parentGenes, set const& recomPositions, int parentChromosome) +{ + genes = parentGenes; +} + +// ---------------------------------------------------------------------------------------- +// Non-inheritance +// For cases where isInherited option is turned off +// In this case, offspring alleles are populated using the initialise functions +// Arguments are still needed to match overloaded function in base class +// ---------------------------------------------------------------------------------------- +void DispersalTrait::reInitialiseGenes(const bool& fromMother, map>> const& parentGenes, set const& recomPositions, int parentChromosome) +{ + DistributionType initialDistribution = pSpeciesTrait->getInitialDistribution(); + map initialParameters = pSpeciesTrait->getInitialParameters(); + + switch (initialDistribution) { + case UNIFORM: + { + if (initialParameters.count(MAX) != 1) + throw logic_error("initial uniform distribution parameter must contain max value (e.g. max= ) \n"); + if (initialParameters.count(MIN) != 1) + throw logic_error("initial uniform distribution parameter must contain min value (e.g. min= ) \n"); + float maxD = initialParameters.find(MAX)->second; + float minD = initialParameters.find(MIN)->second; + initialiseUniform(minD, maxD); + break; + } + case NORMAL: + { + if (initialParameters.count(MEAN) != 1) + throw logic_error("initial normal distribution parameter must contain mean value (e.g. mean= ) \n"); + if (initialParameters.count(SD) != 1) + throw logic_error("initial normal distribution parameter must contain sdev value (e.g. sdev= ) \n"); + float mean = initialParameters.find(MEAN)->second; + float sd = initialParameters.find(SD)->second; + initialiseNormal(mean, sd); + break; + } + default: + { + throw logic_error("wrong parameter value for parameter \"initialisation of dispersal trait\", must be uniform/normal \n"); + break; //should return false + } + } +} + +// ---------------------------------------------------------------------------------------- +// Dispersal initialisation options +// ---------------------------------------------------------------------------------------- +void DispersalTrait::initialiseNormal(float mean, float sd) { + + const set genePositions = pSpeciesTrait->getGenePositions(); + short ploidy = pSpeciesTrait->getPloidy(); + + for (auto position : genePositions) { + vector> newAllelePair; + for (int i = 0; i < ploidy; i++) { + float alleleVal = pRandom->Normal(mean, sd); + newAllelePair.emplace_back(make_shared(alleleVal, dispDominanceFactor)); + } + genes.insert(make_pair(position, newAllelePair)); + } +} + +void DispersalTrait::initialiseUniform(float min, float max) { + + const set genePositions = pSpeciesTrait->getGenePositions(); + short ploidy = pSpeciesTrait->getPloidy(); + + for (auto position : genePositions) { + vector> newAllelePair; + for (int i = 0; i < ploidy; i++) { + float alleleVal = pRandom->FRandom(min, max); + newAllelePair.emplace_back(make_shared(alleleVal, dispDominanceFactor)); + } + genes.insert(make_pair(position, newAllelePair)); + } +} + +// ---------------------------------------------------------------------------------------- +// Dispersal gene expression options +// ---------------------------------------------------------------------------------------- +float DispersalTrait::expressAdditive() { + + float phenotype = 0.0; + + for (auto const& [locus, allelePair] : genes) + { + for (const std::shared_ptr m : allelePair) + phenotype += m->getAlleleValue(); + } + trimPhenotype(phenotype); + return phenotype; +} + +float DispersalTrait::expressAverage() { + + int positionsSize = pSpeciesTrait->getPositionsSize(); + short ploidy = pSpeciesTrait->getPloidy(); + float phenotype = 0.0; + + for (auto const& [locus, allelePair] : genes) + { + for (auto& m : allelePair) + phenotype += m->getAlleleValue(); + } + phenotype /= positionsSize * ploidy; + trimPhenotype(phenotype); + return phenotype; +} + +void DispersalTrait::trimPhenotype(float& val) { + const float minPositiveVal = 1e-06; + switch (pSpeciesTrait->getTraitType()) + { + // Values bound between 0 and 1 + case E_D0_F: case E_D0_M: case E_D0: + case S_S0_F: case S_S0_M: case S_S0: + case KERNEL_PROBABILITY_F: case KERNEL_PROBABILITY_M: case KERNEL_PROBABILITY: + case CRW_STEPCORRELATION: + { + if (val < 0.0) val = 0; + else if (val > 1.0) val = 1.0; + break; + } + // Positive values + case KERNEL_MEANDIST_1_F: case KERNEL_MEANDIST_1_M: case KERNEL_MEANDIST_1: + case KERNEL_MEANDIST_2_F: case KERNEL_MEANDIST_2_M: case KERNEL_MEANDIST_2: + case CRW_STEPLENGTH: + { + if (val < 0.0) val = 0; + break; + } + // Strictly positive values + case E_ALPHA_F: case E_ALPHA_M: case E_ALPHA: + case S_ALPHA_F: case S_ALPHA_M: case S_ALPHA: + case SMS_ALPHADB: + { + if (val <= 0.0) val = minPositiveVal; + break; + } + // Minimum 1 + case SMS_DP: + case SMS_GB: + { + if (val <= 1.0) val = 1.0; + break; + } + // Not bound + case E_BETA_F: case E_BETA_M: case E_BETA: + case S_BETA_F: case S_BETA_M: case S_BETA: + case SMS_BETADB: + { + break; + } + default: + break; + } +} + +// ---------------------------------------------------------------------------------------- +// Get allele value at locus +// ---------------------------------------------------------------------------------------- +float DispersalTrait::getAlleleValueAtLocus(short whichChromosome, int position) const { + + auto it = genes.find(position); + if (it == genes.end()) + throw runtime_error("The Dispersal locus queried for its allele value does not exist."); + return it->second[whichChromosome].get()->getAlleleValue(); +} + +float DispersalTrait::getDomCoefAtLocus(short whichChromosome, int position) const { + auto it = genes.find(position); + if (it == genes.end()) + throw runtime_error("The genetic load locus queried for its dominance coefficient does not exist."); + return it->second[whichChromosome]->getDominanceCoef(); +} + +#ifdef UNIT_TESTS + +// Create a default set of alleles for testing +// +// Shorthand function to manually set genotypes for dispersal and +// genetic fitness traits, instead of having to manipulate mutations. +map>> createTestGenotype( + const int genomeSz, const bool isDiploid, + const float valAlleleA, + const float valAlleleB, + const float domCoeffA, + const float domCoeffB +) { + vector> gene(isDiploid ? 2 : 1); + if (isDiploid) { + gene[0] = make_shared(valAlleleA, domCoeffA); + gene[1] = make_shared(valAlleleB, domCoeffB); + } + else { + gene[0] = make_shared(valAlleleA, domCoeffA); + } + map>> genotype; + for (int i = 0; i < genomeSz; i++) { + genotype.emplace(i, gene); + } + return genotype; +} +#endif // UNIT_TESTS diff --git a/DispersalTrait.h b/DispersalTrait.h new file mode 100644 index 0000000..fc5a4cc --- /dev/null +++ b/DispersalTrait.h @@ -0,0 +1,129 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + +#ifndef DISPTRAITH +#define DISPTRAITH + +#include +#include +#include +#include + +#include "QuantitativeTrait.h" + +using namespace std; + +// Dispersal trait +// +// That is, all evolvable that control emigration, transfer and settlement +class DispersalTrait : public QuantitativeTrait { + +public: + + // Initialisation constructor, set initial values and immutable features + DispersalTrait(SpeciesTrait* P); + + // Inheritance constructor, copies pointers to immutable features when cloning from parent + DispersalTrait(const DispersalTrait& T); + + // Make a shallow copy to pass to offspring trait + // Return new pointer to new trait created by inheritance c'tor + // This avoids copying shared attributes: distributions and parameters + virtual unique_ptr clone() const override { return std::make_unique(*this); } + + virtual ~DispersalTrait() { } + + // Getters + int getNLoci() const override { return pSpeciesTrait->getPositionsSize(); } + float getMutationRate() const override { return pSpeciesTrait->getMutationRate(); } + bool isInherited() const override { return pSpeciesTrait->isInherited(); } + + map>>& getGenes() { return genes; } // returning reference, receiver must be const + + void mutate() override { (this->*_mutate_func_ptr) (); } + float express() override { return (this->*_express_func_ptr) (); } + void inheritGenes(const bool& fromMother, QuantitativeTrait* parent, set const& recomPositions, int startingChromosome) override; + + float getAlleleValueAtLocus(short chromosome, int i) const override; + float getDomCoefAtLocus(short chromosome, int position) const override; + +#ifdef UNIT_TESTS // for testing only + void overwriteGenes(map>> genSeq) { + genes = genSeq; + } + void triggerInherit( + // inheritGenes requires passing a QuantitativeTrait, unfeasible in tests + const bool& fromMother, + map>> const& parentGenes, + set const& recomPositions, + int startChr) { + (this->*_inherit_func_ptr)(fromMother, parentGenes, recomPositions, startChr); + } +#endif + +private: + + const double dispDominanceFactor = 1.0; // no dominance for Dispersal traits (yet?) + + // > + map>> genes; + + // Initialisation + void initialiseUniform(float min, float max); + void initialiseNormal(float mean, float sd); + + // Immutable features, set at initialisation + // and passed down to every subsequent trait copy + //// Species-level trait attributes, invariant across individuals + SpeciesTrait* pSpeciesTrait; + //// Species-level trait functions + void (DispersalTrait::* _mutate_func_ptr) (void); + void (DispersalTrait::* _inherit_func_ptr) (const bool& fromMother, map>> const& parent, set const& recomPositions, int parentChromosome); + float (DispersalTrait::* _express_func_ptr) (void); + + // Possible values for immutable functions + //// Inheritance + void inheritDiploid(const bool& fromMother, map>> const& parent, set const& recomPositions, int parentChromosome); + void inheritHaploid(const bool& fromMother, map>> const& parent, set const& recomPositions, int parentChromosome); + void reInitialiseGenes(const bool& fromMother, map>> const& parentMutations, set const& recomPositions, int parentChromosome); + //// Mutation + void mutateUniform(); + void mutateNormal(); + void trimPhenotype(float& phenotype); + //// Gene expression + float expressAverage(); + float expressAdditive(); +}; + +#ifdef UNIT_TESTS +// Test utilities + +map>> createTestGenotype( + const int genomeSz, const bool isDiploid, + const float valAlleleA, + const float valAlleleB = -99.9, // allow no value for haploids + const float domCoeffA = 1.0, // default for dispersal traits + const float domCoeffB = 1.0 +); +#endif // UNIT_TESTS + +#endif // DISPTRAITH diff --git a/FractalGenerator.cpp b/FractalGenerator.cpp index 7a5ccc3..8650ef4 100644 --- a/FractalGenerator.cpp +++ b/FractalGenerator.cpp @@ -1,26 +1,26 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * --------------------------------------------------------------------------*/ - - -//--------------------------------------------------------------------------- + + + + //--------------------------------------------------------------------------- #include "FractalGenerator.h" //--------------------------------------------------------------------------- @@ -29,211 +29,195 @@ vector patches; //----- Landscape creation -------------------------------------------------- -land::land(): x_coord(0), y_coord(0), value(0.0), avail(0) {} +land::land() : x_coord(0), y_coord(0), value(0.0), avail(0) {} bool compare(const land& z, const land& zz) //compares only the values of the cells { -return z.value < zz.value; + return z.value < zz.value; } -vector& fractal_landscape(int X,int Y,double Hurst,double prop, - double maxValue,double minValue) +vector& fractal_landscape(int X, int Y, double Hurst, double prop, + double maxValue, double minValue) { -#if RSDEBUG -DEBUGLOG << "fractal_landscape(): X=" << X << " Y=" << Y - << " Hurst=" << Hurst << " prop=" << prop - << " maxValue=" << maxValue << " minValue=" << minValue - << endl; -#endif - -int ii, jj, x, y; -int ix, iy; -//int x0, y0, size, kx, kx2, ky, ky2; -int kx,kx2,ky,ky2; - -double range; //range to draw random numbers at each iteration -double nx, ny; -double i, j; -int Nx = X; -int Ny = Y; - -double ran[5]; // to store each time the 5 random numbers for the random displacement - -int Nno; // number of cells NON suitable as habitat - -// exponents used to obtain the landscape dimensions -double pow2x = log(((double)X-1.0))/log(2.0); -double pow2y = log(((double)Y-1.0))/log(2.0); - -double **arena = new double *[X]; -for(ii = 0; ii < X; ii++) { - arena[ii] = new double[Y]; -} -patches.clear(); -// initialise all the landscape with zeroes -for (jj = 0; jj < X; jj++) { - for (ii = 0; ii < Y; ii++) { - arena[jj][ii]=0; - } -} + int ii, jj, x, y; + int ix, iy; + //int x0, y0, size, kx, kx2, ky, ky2; + int kx, kx2, ky, ky2; -// initialisation of the four corners -arena[0][0] = 1.0 + pRandom->Random() * (maxValue-1.0); -arena[0][Y-1] = 1.0 + pRandom->Random() * (maxValue-1.0); -arena[X-1][0] = 1.0 + pRandom->Random() * (maxValue-1.0); -arena[X-1][Y-1] = 1.0 + pRandom->Random() * (maxValue-1.0); + double range; //range to draw random numbers at each iteration + double nx, ny; + double i, j; + int Nx = X; + int Ny = Y; -/////////////MIDPOINT DISPLACEMENT ALGORITHM////////////////////////////////// -kx = (Nx-1) / 2; -kx2 = 2 * kx; -ky = (Ny-1) / 2; -ky2 = 2 * ky; + double ran[5]; // to store each time the 5 random numbers for the random displacement -for (ii = 0; ii < 5; ii++) //random displacement -{ - ran[ii] = 1.0 + pRandom->Random() * (maxValue-1.0); -} + int Nno; // number of cells NON suitable as habitat -//The diamond step: -arena[kx][ky] = ((arena[0][0] + arena[0][ky2] + arena[kx2][0] + arena[kx2][ky2])/4) + ran[0]; + // exponents used to obtain the landscape dimensions + double pow2x = log(((double)X - 1.0)) / log(2.0); + double pow2y = log(((double)Y - 1.0)) / log(2.0); -//The square step: -//left -arena[0][ky] = ((arena[0][0] +arena[0][ky2] + arena[kx][ky]) / 3) + ran[1]; -//top -arena[kx][0] = ((arena[0][0] + arena[kx][ky] + arena[kx2][0]) / 3) + ran[2]; -//right -arena[kx2][ky] = ((arena[kx2][0] + arena[kx][ky] + arena[kx2][ky2]) / 3) + ran[3]; -//bottom -arena[kx][ky2] = ((arena[0][ky2] + arena[kx][ky] +arena[kx2][ky2]) / 3) + ran[4]; + double** arena = new double* [X]; + for (ii = 0; ii < X; ii++) { + arena[ii] = new double[Y]; + } -range = maxValue*pow(2,-Hurst); + patches.clear(); + // initialise all the landscape with zeroes + for (jj = 0; jj < X; jj++) { + for (ii = 0; ii < Y; ii++) { + arena[jj][ii] = 0; + } + } -i = pow2x-1; -j = pow2y-1; + // initialisation of the four corners + arena[0][0] = 1.0 + pRandom->Random() * (maxValue - 1.0); + arena[0][Y - 1] = 1.0 + pRandom->Random() * (maxValue - 1.0); + arena[X - 1][0] = 1.0 + pRandom->Random() * (maxValue - 1.0); + arena[X - 1][Y - 1] = 1.0 + pRandom->Random() * (maxValue - 1.0); -while (i > 0) { - nx = pow(2,i)+1; - kx = (int)((nx-1) / 2); + /////////////MIDPOINT DISPLACEMENT ALGORITHM////////////////////////////////// + kx = (Nx - 1) / 2; kx2 = 2 * kx; - - ny = pow(2,j)+1; - ky = (int)((ny-1) / 2); + ky = (Ny - 1) / 2; ky2 = 2 * ky; - ix = 0; - while (ix <= (Nx-nx)) { - iy = 0; - while (iy <= (Ny-ny)) { - for (ii = 0; ii < 5; ii++) //random displacement - { - ran[ii] = (int)(pRandom->Random() * 2.0 * range - range); + for (ii = 0; ii < 5; ii++) //random displacement + { + ran[ii] = 1.0 + pRandom->Random() * (maxValue - 1.0); + } + + //The diamond step: + arena[kx][ky] = ((arena[0][0] + arena[0][ky2] + arena[kx2][0] + arena[kx2][ky2]) / 4) + ran[0]; + + //The square step: + //left + arena[0][ky] = ((arena[0][0] + arena[0][ky2] + arena[kx][ky]) / 3) + ran[1]; + //top + arena[kx][0] = ((arena[0][0] + arena[kx][ky] + arena[kx2][0]) / 3) + ran[2]; + //right + arena[kx2][ky] = ((arena[kx2][0] + arena[kx][ky] + arena[kx2][ky2]) / 3) + ran[3]; + //bottom + arena[kx][ky2] = ((arena[0][ky2] + arena[kx][ky] + arena[kx2][ky2]) / 3) + ran[4]; + + range = maxValue * pow(2, -Hurst); + + i = pow2x - 1; + j = pow2y - 1; + + while (i > 0) { + nx = pow(2, i) + 1; + kx = (int)((nx - 1) / 2); + kx2 = 2 * kx; + + ny = pow(2, j) + 1; + ky = (int)((ny - 1) / 2); + ky2 = 2 * ky; + + ix = 0; + while (ix <= (Nx - nx)) { + iy = 0; + while (iy <= (Ny - ny)) { + for (ii = 0; ii < 5; ii++) //random displacement + { + ran[ii] = (int)(pRandom->Random() * 2.0 * range - range); + } + //The diamond step: + + arena[ix + kx][iy + ky] = ((arena[ix][iy] + arena[ix][iy + ky2] + arena[ix + ky2][iy] + + arena[ix + kx2][iy + ky2]) / 4) + ran[0]; + if (arena[ix + kx][iy + ky] < 1) arena[ix + kx][iy + ky] = 1; + + //The square step: + //left + arena[ix][iy + ky] = ((arena[ix][iy] + arena[ix][iy + ky2] + arena[ix + kx][iy + ky]) / 3) + + ran[1]; + if (arena[ix][iy + ky] < 1) arena[ix][iy + ky] = 1; + //top + arena[ix + kx][iy] = ((arena[ix][iy] + arena[ix + kx][iy + ky] + arena[ix + kx2][iy]) / 3) + + ran[2]; + if (arena[ix + kx][iy] < 1) arena[ix + kx][iy] = 1; + //right + arena[ix + kx2][iy + ky] = ((arena[ix + kx2][iy] + arena[ix + kx][iy + ky] + + arena[ix + kx2][iy + ky2]) / 3) + ran[3]; + if (arena[ix + kx2][iy + ky] < 1) arena[ix + kx2][iy + ky] = 1; + //bottom + arena[ix + kx][iy + ky2] = ((arena[ix][iy + ky2] + arena[ix + kx][iy + ky] + + arena[ix + kx2][iy + ky2]) / 3) + ran[4]; + if (arena[ix + kx][iy + ky2] < 1) arena[ix + kx][iy + ky2] = 1; + + iy += ((int)ny - 1); } - //The diamond step: - - arena[ix+kx][iy+ky] = ((arena[ix][iy] + arena[ix][iy+ky2] + arena[ix+ky2][iy] - + arena[ix+kx2][iy+ky2])/ 4) + ran[0]; - if (arena[ix+kx][iy+ky] < 1) arena[ix+kx][iy+ky] = 1; - - //The square step: - //left - arena[ix][iy+ky] =((arena[ix][iy] +arena[ix][iy+ky2] + arena[ix+kx][iy+ky])/3) - + ran[1]; - if (arena[ix][iy+ky] < 1) arena[ix][iy+ky] = 1; - //top - arena[ix+kx][iy] =((arena[ix][iy] + arena[ix+kx][iy+ky] + arena[ix+kx2][iy])/3) - + ran[2]; - if (arena[ix+kx][iy] < 1) arena[ix+kx][iy] = 1; - //right - arena[ix+kx2][iy+ky] = ((arena[ix+kx2][iy] + arena[ix+kx][iy+ky] + - arena[ix+kx2][iy+ky2]) / 3) + ran[3]; - if (arena[ix+kx2][iy+ky] < 1) arena[ix+kx2][iy+ky] = 1; - //bottom - arena[ix+kx][iy+ky2] = ((arena[ix][iy+ky2] + arena[ix+kx][iy+ky] + - arena[ix+kx2][iy+ky2]) / 3) + ran[4]; - if (arena[ix+kx][iy+ky2] < 1) arena[ix+kx][iy+ky2] = 1; - - iy += ((int)ny-1); + ix += ((int)nx - 1); } - ix += ((int)nx-1); - } - if (i==j) j--; - i--; + if (i == j) j--; + i--; - range = range*pow(2,-Hurst); //reduce the random number range -} + range = range * pow(2, -Hurst); //reduce the random number range + } -// Now all the cells will be sorted and the Nno cells with the lower carrying -// capacity will be set as matrix, i.e. with K = 0 + // Now all the cells will be sorted and the Nno cells with the lower carrying + // capacity will be set as matrix, i.e. with K = 0 -land *patch; + land* patch; -for (x = 0; x < X; x++) // put all the cells with their values in a vector -{ - for (y = 0; y < Y; y++) + for (x = 0; x < X; x++) // put all the cells with their values in a vector { - patch = new land; - patch->x_coord = x; - patch->y_coord = y; - patch->value = (float)arena[x][y]; - patch->avail = 1; + for (y = 0; y < Y; y++) + { + patch = new land; + patch->x_coord = x; + patch->y_coord = y; + patch->value = (float)arena[x][y]; + patch->avail = 1; - patches.push_back(*patch); + patches.push_back(*patch); - delete patch; + delete patch; + } } -} -sort(patches.begin(),patches.end(),compare); // sorts the vector + sort(patches.begin(), patches.end(), compare); // sorts the vector -Nno = (int)(prop*X*Y); -for (ii = 0; ii < Nno; ii++) -{ - patches[ii].value = 0.0; - patches[ii].avail = 0; -} + Nno = (int)(prop * X * Y); + for (ii = 0; ii < Nno; ii++) + { + patches[ii].value = 0.0; + patches[ii].avail = 0; + } -double min = (double)patches[Nno].value; // variables for the rescaling -double max = (double)patches[X*Y-1].value; + double min = (double)patches[Nno].value; // variables for the rescaling + double max = (double)patches[X * Y - 1].value; -double diff = max - min; -double diffK = maxValue-minValue; -double new_value; + double diff = max - min; + double diffK = maxValue - minValue; + double new_value; -vector::iterator iter = patches.begin(); -while (iter != patches.end()) -{ - if (iter->value > 0) // rescale to a range of K between Kmin and Kmax + vector::iterator iter = patches.begin(); + while (iter != patches.end()) { - new_value = maxValue - diffK * (max - (double)iter->value) / diff; + if (iter->value > 0) // rescale to a range of K between Kmin and Kmax + { + new_value = maxValue - diffK * (max - (double)iter->value) / diff; - iter->value = (float)new_value; - } - else iter->value = 0; + iter->value = (float)new_value; + } + else iter->value = 0; - iter++; -} + iter++; + } -if (arena != NULL) { -#if RSDEBUG -//DebugGUI(("fractal_landscape(): arena=" + Int2Str((int)arena) -// + " X=" + Int2Str(X) + " Y=" + Int2Str(Y) -// ).c_str()); -#endif - for(ii = 0; ii < X; ii++) { -#if RSDEBUG -//DebugGUI(("fractal_landscape(): ii=" + Int2Str(ii) -// + " arena[ii]=" + Int2Str((int)arena[ii]) -// ).c_str()); -#endif - delete[] arena[ii]; + if (arena != NULL) { + for (ii = 0; ii < X; ii++) { + delete[] arena[ii]; + } + delete[] arena; } - delete[] arena; -} -return patches; + return patches; } diff --git a/FractalGenerator.h b/FractalGenerator.h index 24acbc7..3a2e216 100644 --- a/FractalGenerator.h +++ b/FractalGenerator.h @@ -1,66 +1,66 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Tho Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * --------------------------------------------------------------------------*/ - - -/*------------------------------------------------------------------------------ -RangeShifter v2.0 FractalGenerator -Implements the midpoint displacement algorithm for generating a fractal Landscape, -following: -Saupe, D. (1988). Algorithms for random fractals. In: The Science of Fractal Images -(eds. Pietgen, H.O. & Saupe, D.). Springer, New York, pp. 71113. + /*------------------------------------------------------------------------------ + RangeShifter v2.0 FractalGenerator -For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. -and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. -Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 + Implements the midpoint displacement algorithm for generating a fractal Landscape, + following: -Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + Saupe, D. (1988). Algorithms for random fractals. In: The Science of Fractal Images + (eds. Pietgen, H.O. & Saupe, D.). Springer, New York, pp. 71113. -Last updated: 15 July 2021 by Anne-Kathleen Malchow -------------------------------------------------------------------------------*/ + For full details of RangeShifter, please see: + Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. + and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial + eco-evolutionary dynamics and species responses to environmental changes. + Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 + + Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + + Last updated: 15 July 2021 by Anne-Kathleen Malchow + + ------------------------------------------------------------------------------*/ #ifndef FractalGeneratorH #define FractalGeneratorH #include #include -//using namespace std; + //using namespace std; #include "Parameters.h" class land { - public: +public: land(); int x_coord; int y_coord; float value; int avail; // if 0 the patch is not available as habitat, if 1 it is - private: +private: }; // IMPORTANT NOTE: X AND Y ARE TRANSPOSED, i.e. X IS THE VERTICAL CO-ORDINATE @@ -76,10 +76,7 @@ vector& fractal_landscape( ); bool compare(const land&, const land&); -extern RSrandom *pRandom; -#if RSDEBUG -extern void DebugGUI(string); -#endif +extern RSrandom* pRandom; //--------------------------------------------------------------------------- #endif diff --git a/GeneticFitnessTrait.cpp b/GeneticFitnessTrait.cpp new file mode 100644 index 0000000..fa1bb55 --- /dev/null +++ b/GeneticFitnessTrait.cpp @@ -0,0 +1,558 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + +#include "GeneticFitnessTrait.h" + +// ---------------------------------------------------------------------------------------- +// Initialisation constructor +// Called when initialising community +// Sets up initial values, and immutable attributes (distributions and parameters) +// that are defined at the species-level +// ---------------------------------------------------------------------------------------- +GeneticFitnessTrait::GeneticFitnessTrait(SpeciesTrait* P) +{ + pSpeciesTrait = P; + ExpressionType expressionType = pSpeciesTrait->getExpressionType(); + + _inherit_func_ptr = (pSpeciesTrait->getPloidy() == 1) ? &GeneticFitnessTrait::inheritHaploid : &GeneticFitnessTrait::inheritDiploid; //this could be changed if we wanted some alternative form of inheritance + + // Set initialisation parameters + DistributionType initialDistribution = pSpeciesTrait->getInitialDistribution(); + map initialParameters = pSpeciesTrait->getInitialParameters(); + switch (initialDistribution) { + case UNIFORM: + { + if (initialParameters.count(MAX) != 1) + throw logic_error("initial uniform distribution parameter must contain max value (e.g. max= ) \n"); + if (initialParameters.count(MIN) != 1) + throw logic_error("initial uniform distribution parameter must contain min value (e.g. min= ) \n"); + break; + } + case NORMAL: + { + if (initialParameters.count(MEAN) != 1) + throw logic_error("initial normal distribution parameter must contain mean value (e.g. mean= ) \n"); + if (initialParameters.count(SD) != 1) + throw logic_error("initial normal distribution parameter must contain sdev value (e.g. sdev= ) \n"); + break; + } + case GAMMA: + { + if (initialParameters.count(SHAPE) != 1) + throw logic_error("genetic load dominance distribution set to gamma so parameters must contain one shape value (e.g. shape= ) \n"); + if (initialParameters.count(SCALE) != 1) + throw logic_error("genetic load dominance distribution set to gamma so parameters must contain one scale value (e.g. scale= ) \n"); + break; + } + case NEGEXP: + { + if (initialParameters.count(MEAN) != 1) + throw logic_error("genetic load dominance distribution set to negative exponential (negative decay) so parameters must contain mean value (e.g. mean= ) \n"); + break; + } + case NONE: // initialise with default (i.e. zero) values + break; + default: + { + throw logic_error("wrong parameter value for parameter \"initialisation of dispersal traits\", must be uniform/normal \n"); + break; + } + } + + DistributionType initDomDistribution = pSpeciesTrait->getInitDomDistribution(); + map initDomParameters = pSpeciesTrait->getInitDomParameters(); + switch (initDomDistribution) { + case UNIFORM: + { + if (initDomParameters.count(MAX) != 1) + throw logic_error("genetic load dominance uniform distribution parameter must contain one max value (e.g. max= ) \n"); + if (initDomParameters.count(MIN) != 1) + throw logic_error("genetic load dominance uniform distribution parameter must contain one min value (e.g. min= ) \n"); + break; + } + case NORMAL: + { + if (initDomParameters.count(MEAN) != 1) + throw logic_error("genetic load dominance distribution set to normal so parameters must contain one mean value (e.g. mean= ) \n"); + if (initDomParameters.count(SD) != 1) + throw logic_error("genetic load dominance distribution set to normal so parameters must contain one sdev value (e.g. sdev= ) \n"); + break; + } + case GAMMA: + { + if (initDomParameters.count(SHAPE) != 1) + throw logic_error("genetic load dominance distribution set to gamma so parameters must contain one shape value (e.g. shape= ) \n"); + if (initDomParameters.count(SCALE) != 1) + throw logic_error("genetic load dominance distribution set to gamma so parameters must contain one scale value (e.g. scale= ) \n"); + break; + } + case NEGEXP: + { + if (initDomParameters.count(MEAN) != 1) + throw logic_error("genetic load dominance distribution set to negative exponential (negative decay) so parameters must contain mean value (e.g. mean= ) \n"); + break; + } + case SCALED: + { + if (initDomParameters.count(MEAN) != 1) + throw logic_error("genetic load dominance distribution set to scaled, so parameters must contain mean dominance value (e.g. mean= ) \n"); + // Set for drawing initial values + setScaledCoeff(initialDistribution, initialParameters); + break; + } + case NONE: // default values, zero-dominance coefficients + break; + default: + { + throw logic_error("wrong parameter value for genetic load dominance model, must be uniform/normal/gamma/negExp/scaled \n"); + break; + } + } + + // Draw initial values + initialise(); + + DistributionType mutationDistribution = pSpeciesTrait->getMutationDistribution(); + map mutationParameters = pSpeciesTrait->getMutationParameters(); + switch (mutationDistribution) { + case UNIFORM: + { + if (mutationParameters.count(MAX) != 1) + throw logic_error("genetic load mutation uniform distribution parameter must contain one max value (e.g. max= ) \n"); + if (mutationParameters.count(MIN) != 1) + throw logic_error("genetic load mutation uniform distribution parameter must contain one min value (e.g. min= ) \n"); + break; + } + case NORMAL: + { + if (mutationParameters.count(MEAN) != 1) + throw logic_error("genetic load mutation distribution set to normal so parameters must contain one mean value (e.g. mean= ) \n"); + if (mutationParameters.count(SD) != 1) + throw logic_error("genetic load mutation distribution set to normal so parameters must contain one sdev value (e.g. sdev= ) \n"); + break; + } + case GAMMA: + { + if (mutationParameters.count(SHAPE) != 1) + throw logic_error("genetic load mutation distribution set to gamma so parameters must contain one shape value (e.g. shape= ) \n"); + if (mutationParameters.count(SCALE) != 1) + throw logic_error("genetic load mutation distribution set to gamma so parameters must contain one scale value (e.g. scale= ) \n"); + break; + } + case NEGEXP: + { + if (mutationParameters.count(MEAN) != 1) + throw logic_error("genetic load mutation distribution set to negative exponential (negative decay) so parameters must contain one mean value (e.g. mean= ) \n"); + break; + } + default: + throw logic_error("wrong parameter value for genetic load mutation model, must be uniform/normal/gamma/negExp \n"); + } + + DistributionType dominanceDistribution = pSpeciesTrait->getDominanceDistribution(); + map dominanceParameters = pSpeciesTrait->getDominanceParameters(); + + switch (dominanceDistribution) { + case UNIFORM: + { + if (dominanceParameters.count(MAX) != 1) + throw logic_error("genetic load dominance uniform distribution parameter must contain one max value (e.g. max= ) \n"); + if (dominanceParameters.count(MIN) != 1) + throw logic_error("genetic load dominance uniform distribution parameter must contain one min value (e.g. min= ) \n"); + break; + } + case NORMAL: + { + if (dominanceParameters.count(MEAN) != 1) + throw logic_error("genetic load dominance distribution set to normal so parameters must contain one mean value (e.g. mean= ) \n"); + if (dominanceParameters.count(SD) != 1) + throw logic_error("genetic load dominance distribution set to normal so parameters must contain one sdev value (e.g. sdev= ) \n"); + break; + } + case GAMMA: + { + if (dominanceParameters.count(SHAPE) != 1) + throw logic_error("genetic load dominance distribution set to gamma so parameters must contain one shape value (e.g. shape= ) \n"); + if (dominanceParameters.count(SCALE) != 1) + throw logic_error("genetic load dominance distribution set to gamma so parameters must contain one scale value (e.g. scale= ) \n"); + break; + } + case NEGEXP: + { + if (dominanceParameters.count(MEAN) != 1) + throw logic_error("genetic load dominance distribution set to negative exponential (negative decay) so parameters must contain mean value (e.g. mean= ) \n"); + break; + } + case SCALED: + { + if (dominanceParameters.count(MEAN) != 1) + throw logic_error("genetic load dominance distribution set to scaled, so parameters must contain mean dominance value (e.g. mean= ) \n"); + + // Set for drawing mutations (overwrite initial value) + setScaledCoeff(mutationDistribution, mutationParameters); + break; + } + default: + { + throw logic_error("wrong parameter value for genetic load dominance model, must be uniform/normal/gamma/negExp/scaled \n"); + break; + } + } +} + +// Calculate mean selection coeff s_d for calculation of k +void GeneticFitnessTrait::setScaledCoeff(const DistributionType& selCoeffDist, const map& selCoeffParams) +{ + switch (selCoeffDist) + { + case UNIFORM: + scaledDomMeanSelCoeff = (selCoeffParams.find(MIN)->second + selCoeffParams.find(MAX)->second) / 2; + break; + case NORMAL: + scaledDomMeanSelCoeff = selCoeffParams.find(MEAN)->second; + break; + case GAMMA: + scaledDomMeanSelCoeff = selCoeffParams.find(SHAPE)->second * selCoeffParams.find(SCALE)->second; + break; + case NEGEXP: + scaledDomMeanSelCoeff = 1 / selCoeffParams.find(MEAN)->second; + break; + case NONE: + throw logic_error("Scaled dominance distribution cannot be used with default allele distribution."); + default: break; + } +} + +// ---------------------------------------------------------------------------------------- +// Inheritance constructor +// Copies immutable features from a parent trait +// Only called via clone() +// ---------------------------------------------------------------------------------------- +GeneticFitnessTrait::GeneticFitnessTrait(const GeneticFitnessTrait& T) : + pSpeciesTrait(T.pSpeciesTrait), + _inherit_func_ptr(T._inherit_func_ptr), + scaledDomMeanSelCoeff(T.scaledDomMeanSelCoeff) +{ + // nothing +} + +void GeneticFitnessTrait::initialise() { + float initSelCoeff; + float initDomCoeff; + short ploidy = pSpeciesTrait->getPloidy(); + auto initDist = pSpeciesTrait->getInitialDistribution(); + auto initParams = pSpeciesTrait->getInitialParameters(); + auto initDomDist = pSpeciesTrait->getInitDomDistribution(); + auto initDomParams = pSpeciesTrait->getInitDomParameters(); + + const set genePositions = pSpeciesTrait->getGenePositions(); + const set initPositions = pSpeciesTrait->getInitPositions(); + + for (auto position : genePositions) { + vector> initialGene(ploidy); + for (int p = 0; p < ploidy; p++) { + initSelCoeff = initDomCoeff = 0.0; + if (initPositions.contains(position)) { + initSelCoeff = drawSelectionCoef(initDist, initParams); + initDomCoeff = drawDominance(initSelCoeff, initDomDist, initDomParams); + } + initialGene[p] = make_shared(initSelCoeff, initDomCoeff); + } + genes.insert(make_pair(position, initialGene)); + } +} + +// ---------------------------------------------------------------------------------------- +// Mutate uniform +// ---------------------------------------------------------------------------------------- +void GeneticFitnessTrait::mutate() +{ + const int positionsSize = pSpeciesTrait->getPositionsSize(); + const auto& genePositions = pSpeciesTrait->getGenePositions(); + const short ploidy = pSpeciesTrait->getPloidy(); + const float mutationRate = pSpeciesTrait->getMutationRate(); + float newSelectionCoef; + float newDominanceCoef; + + auto rng = pRandom->getRNG(); + + for (int p = 0; p < ploidy; p++) { + // Determine nb of mutations + unsigned int NbMut = pRandom->Binomial(positionsSize, mutationRate); + + if (NbMut > 0) { + vector mutationPositions; + // Draw which positions mutate + sample(genePositions.begin(), genePositions.end(), std::back_inserter(mutationPositions), + NbMut, rng); + + for (int m : mutationPositions) { + auto it = genes.find(m); + if (it == genes.end()) + throw runtime_error("Locus sampled for mutation doesn't exist."); + newSelectionCoef = drawSelectionCoef( + pSpeciesTrait->getMutationDistribution(), + pSpeciesTrait->getMutationParameters() + ); + newDominanceCoef = drawDominance( + newSelectionCoef, + pSpeciesTrait->getDominanceDistribution(), + pSpeciesTrait->getDominanceParameters() + ); + it->second[p] = make_shared(newSelectionCoef, newDominanceCoef); + } + } + } +} + +// ---------------------------------------------------------------------------------------- +// get dominance value for new mutation +// ---------------------------------------------------------------------------------------- +float GeneticFitnessTrait::drawDominance(float selCoef, const DistributionType& domDist, const map& domParams) { + + float h = 0.0; + switch (domDist) { + case UNIFORM: + { + float maxD = domParams.find(MAX)->second; + float minD = domParams.find(MIN)->second; + h = pRandom->FRandom(minD, maxD); + break; + } + case NORMAL: + { + const float mean = domParams.find(MEAN)->second; + const float sd = domParams.find(SD)->second; + do { + h = static_cast(pRandom->Normal(mean, sd)); + } while (h <= 0.0); + break; + } + case GAMMA: + { + const float shape = domParams.find(SHAPE)->second; + const float scale = domParams.find(SCALE)->second; + h = static_cast(pRandom->Gamma(shape, scale)); + break; + } + case NEGEXP: + { + const float mean = domParams.find(MEAN)->second; + h = static_cast(pRandom->NegExp(mean)); + break; + } + case SCALED: + { + const float h_d = domParams.find(MEAN)->second; + const float k = -log(2 * h_d) / scaledDomMeanSelCoeff; + const float max = exp(-k * selCoef); + h = pRandom->FRandom(0, max); + break; + } + case NONE: + { + // nothing, s remains 0.0 + break; + } + default: + { + throw logic_error("wrong parameter value for genetic load dominance model, must be uniform/normal/gamma/negExp/scaled/none \n"); + break; + } + } + return h; +} + +// ---------------------------------------------------------------------------------------- +// Get selection coefficient for new mutation +// +// Selection coefficients will usually be between 0 and 1, but may, +// if the mutation distribution enable it, take a negative value +// down to -1 representing the effect of beneficial mutations +// ---------------------------------------------------------------------------------------- +float GeneticFitnessTrait::drawSelectionCoef(const DistributionType& mutationDistribution, const map& mutationParameters) { + + float s = 0.0; // default selection coefficient is 0 + + switch (mutationDistribution) { + case UNIFORM: + { + float maxD = mutationParameters.find(MAX)->second; + float minD = mutationParameters.find(MIN)->second; + s = pRandom->FRandom(minD, maxD); // no check here, min and max should already be constrained to valid values + break; + } + case NORMAL: + { + const float mean = mutationParameters.find(MEAN)->second; + const float sd = mutationParameters.find(SD)->second; + do { + s = static_cast(pRandom->Normal(mean, sd)); + } while (!pSpeciesTrait->isValidTraitVal(s)); + break; + } + case GAMMA: + { + const float shape = mutationParameters.find(SHAPE)->second; + const float scale = mutationParameters.find(SCALE)->second; + do { + s = static_cast(pRandom->Gamma(shape, scale)); + } while (!pSpeciesTrait->isValidTraitVal(s)); + break; + } + case NEGEXP: + { + const float mean = mutationParameters.find(MEAN)->second; + do { + s = static_cast(pRandom->NegExp(mean)); + } while (!pSpeciesTrait->isValidTraitVal(s)); + break; + } + case NONE: + { + // nothing, s remains 0.0 + break; + } + default: + { + throw logic_error("wrong parameter value for genetic load mutation model, must be uniform/normal/gamma/negExp/scaled/none \n"); + break; + } + } + return s; +} + + +// ---------------------------------------------------------------------------------------- +// Wrapper to inheritance function +// ---------------------------------------------------------------------------------------- +void GeneticFitnessTrait::inheritGenes(const bool& fromMother, QuantitativeTrait* parentTrait, set const& recomPositions, int startingChromosome) +{ + auto parentCast = dynamic_cast (parentTrait); // must convert QuantitativeTrait to GeneticFitnessTrait + const auto& parent_seq = parentCast->getGenes(); + (this->*_inherit_func_ptr) (fromMother, parent_seq, recomPositions, startingChromosome); +} + +// ---------------------------------------------------------------------------------------- +// Inheritance for diploid, sexual species +// Called once for each parent. Given a list of recombinant sites, +// populates offspring genes with appropriate parent alleles +// Assumes mother genes are inherited first +// ---------------------------------------------------------------------------------------- +void GeneticFitnessTrait::inheritDiploid(const bool& fromMother, map>> const& parentGenes, set const& recomPositions, int parentChromosome) { + + const int lastPosition = parentGenes.rbegin()->first; + auto recomIt = recomPositions.lower_bound(parentGenes.begin()->first); + // If no recombination sites, only breakpoint is last position + // i.e., no recombination occurs + int nextBreakpoint = recomIt == recomPositions.end() ? lastPosition : *recomIt; + + // Is the first parent gene position already recombinant? + auto distance = std::distance(recomPositions.begin(), recomIt); + if (distance % 2 != 0) + parentChromosome = 1 - parentChromosome; // switch chromosome + + for (auto const& [locus, allelePair] : parentGenes) { + + // Switch chromosome if locus is past recombination site + while (locus > nextBreakpoint) { + parentChromosome = 1 - parentChromosome; + std::advance(recomIt, 1); // go to next recombination site + nextBreakpoint = recomIt == recomPositions.end() ? lastPosition : *recomIt; + } + + if (locus <= nextBreakpoint) { + auto& parentAllele = allelePair[parentChromosome]; + auto itGene = genes.find(locus); + if (itGene == genes.end()) { + // locus does not exist yet, create and initialise it + if (!fromMother) throw runtime_error("Father-inherited locus does not exist."); + vector> newAllelePair(2); // always diploid + newAllelePair[sex_t::FEM] = parentAllele; + genes.insert(make_pair(locus, newAllelePair)); + } + else { // father, locus already exists + if (fromMother) throw runtime_error("Mother-inherited locus already exists."); + itGene->second[sex_t::MAL] = parentAllele; + } + } + } +} + +// ---------------------------------------------------------------------------------------- +// Inheritance for haploid, asexual species +// Simply pass down parent genes +// Arguments are still needed to match overloaded function in base class +// ---------------------------------------------------------------------------------------- +void GeneticFitnessTrait::inheritHaploid(const bool& fromMother, map>> const& parentGenes, set const& recomPositions, int parentChromosome) +{ + genes = parentGenes; +} + +// ---------------------------------------------------------------------------------------- +// Expression genetic load +// ---------------------------------------------------------------------------------------- +float GeneticFitnessTrait::express() { + + float phenotype = 1.0; // base chance of viability + float sA, sB, hA, hB, sumDomCoeffs, hLocus; + + for (auto const& [locus, pAllelePair] : genes) + { + shared_ptr pAlleleA = pAllelePair[0] == 0 ? wildType : pAllelePair[0]; + + sA = pAlleleA->getAlleleValue(); + hA = pAlleleA->getDominanceCoef(); + + if (pSpeciesTrait->getPloidy() == 2) { + shared_ptr pAlleleB = pAllelePair[1] == 0 ? wildType : pAllelePair[1]; + sB = pAlleleB->getAlleleValue(); + hB = pAlleleB->getDominanceCoef(); + + sumDomCoeffs = hA + hB; + hLocus = sumDomCoeffs == 0.0 ? 0.5 : hA / sumDomCoeffs; + phenotype *= 1 - hLocus * sA - (1 - hLocus) * sB; + } + else { + phenotype *= 1 - sA; + } + } + return phenotype; +} + +// ---------------------------------------------------------------------------------------- +// Get allele value at locus +// ---------------------------------------------------------------------------------------- +float GeneticFitnessTrait::getAlleleValueAtLocus(short whichChromosome, int position) const { + + auto it = genes.find(position); + if (it == genes.end()) + throw runtime_error("The genetic load locus queried for its allele value does not exist."); + return it->second[whichChromosome] == 0 ? wildType->getAlleleValue() : it->second[whichChromosome]->getAlleleValue(); +} + +float GeneticFitnessTrait::getDomCoefAtLocus(short whichChromosome, int position) const { + auto it = genes.find(position); + if (it == genes.end()) + throw runtime_error("The genetic load locus queried for its dominance coefficient does not exist."); + return it->second[whichChromosome] == 0 ? wildType->getDominanceCoef() : it->second[whichChromosome]->getDominanceCoef(); +} diff --git a/GeneticFitnessTrait.h b/GeneticFitnessTrait.h new file mode 100644 index 0000000..cb81eff --- /dev/null +++ b/GeneticFitnessTrait.h @@ -0,0 +1,111 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + +#ifndef GENETICFITNESSH +#define GENETICFITNESSH + +#include +#include +#include +#include +#include + +#include "QuantitativeTrait.h" + +using namespace std; + +// Genetic Load trait +// +// Alleles contribute to their value to reducing +// offspring viability. Alleles start with value +// zero but increase through simulation via mutations. +// There can be up to five genetic load traits. +class GeneticFitnessTrait : public QuantitativeTrait { + +public: + + // Initialisation constructor, set initial values and immutable features + GeneticFitnessTrait(SpeciesTrait* P); + + // Inheritance constructor, copies pointers to immutable features when cloning from parent + GeneticFitnessTrait(const GeneticFitnessTrait& T); + + // Make a shallow copy to pass to offspring trait + // Return new pointer to new trait created by inheritance c'tor + // This avoids copying shared attributes: distributions and parameters + virtual unique_ptr clone() const override { return std::make_unique(*this); } + + virtual ~GeneticFitnessTrait() { } + + // Getters + virtual int getNLoci() const override { return pSpeciesTrait->getPositionsSize(); } + float getMutationRate() const override { return pSpeciesTrait->getMutationRate(); } + bool isInherited() const override { return pSpeciesTrait->isInherited(); } + + map>>& getGenes() { return genes; } // returning reference, reciever must be const + + virtual void mutate() override; + virtual float express(); + virtual void inheritGenes(const bool& fromMother, QuantitativeTrait* parent, set const& recomPositions, int startingChromosome) override; + + virtual float getAlleleValueAtLocus(short chromosome, int position) const override; + virtual float getDomCoefAtLocus(short chromosome, int position) const override; + +private: + + // Default allele has value 0 and dominance 0 + inline static shared_ptr wildType = make_shared(0.0, 0.0); + + // > + map>> genes; // position > + + // Initialisation + void initialise(); + + void setScaledCoeff(const DistributionType& selCoeffDist, const map& selCoeffParams); + + // Mutation + float scaledDomMeanSelCoeff = 0; // s_d, only for scaled dominance distribution + float drawDominance( + float selCoef, + const DistributionType& domDist, + const map& domParams + ); + float drawSelectionCoef( + const DistributionType& mutationDistribution, + const map& mutationParameters + ); + + // Immutable features, set at initialisation + // and passed down to every subsequent trait copy + //// Species-level trait attributes, invariant across individuals + SpeciesTrait* pSpeciesTrait; + //// Species-level trait functions + void (GeneticFitnessTrait::* _inherit_func_ptr) (const bool& fromMother, map>> const& parent, set const& recomPositions, int parentChromosome); + + // Possible values for immutable functions + //// Inheritance + void inheritDiploid(const bool& fromMother, map>> const& parent, set const& recomPositions, int parentChromosome); + void inheritHaploid(const bool& fromMother, map>> const& parent, set const& recomPositions, int parentChromosome); + +}; +#endif // GENETICFITNESSH diff --git a/Genome.cpp b/Genome.cpp deleted file mode 100644 index 59a68b1..0000000 --- a/Genome.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * - * This file is part of RangeShifter. - * - * RangeShifter 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. - * - * RangeShifter 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 RangeShifter. If not, see . - * - --------------------------------------------------------------------------*/ - -#include "Genome.h" -//--------------------------------------------------------------------------- - -ofstream outGenetic; - -//--------------------------------------------------------------------------- - -Chromosome::Chromosome(int nloc) -{ - if (nloc > 0) nloci = nloc; else nloci = 1; - pLoci = new locus[nloci]; - for (int i = 0; i < nloci; i++) { - pLoci[i].allele[0] = pLoci[i].allele[1] = 0; - } -} - -Chromosome::~Chromosome() { - if (pLoci != 0) { - delete[] pLoci; pLoci = NULL; - } -} - -short Chromosome::nLoci(void) { return nloci; } - -locus Chromosome::alleles(const int loc) { // return allele values at a specified locus - locus l; l.allele[0] = l.allele[1] = 0; - if (loc >= 0 && loc < nloci) { - l.allele[0] = pLoci[loc].allele[0]; l.allele[1] = pLoci[loc].allele[1]; - } - return l; -} - -double Chromosome::additive(const bool diploid) { - int sum = 0; - for (int i = 0; i < nloci; i++) { - sum += pLoci[i].allele[0]; - if (diploid) sum += pLoci[i].allele[1]; - } - return (double)sum / INTBASE; -} - -double Chromosome::meanvalue(const bool diploid) { - int sum = 0; - double mean; - for (int i = 0; i < nloci; i++) { - sum += pLoci[i].allele[0]; - if (diploid) sum += pLoci[i].allele[1]; - } - mean = (double)sum / (double)nloci; - if (diploid) mean /= 2.0; - mean /= INTBASE; - return mean; -} - -double Chromosome::additive(const short loc, const bool diploid) { - int sum = 0; - sum += pLoci[loc].allele[0]; - if (diploid) sum += pLoci[loc].allele[1]; - return (double)sum / INTBASE; -} - -// Set up chromosome at simulation initialisation -void Chromosome::initialise(const double mean, const double sd, - const bool diploid) { - double avalue; - double intbase = INTBASE; - - for (int i = 0; i < nloci; i++) { - avalue = pRandom->Normal(mean, sd); - if (avalue > 0.0) - pLoci[i].allele[0] = (int)(avalue * intbase + 0.5); - else - pLoci[i].allele[0] = (int)(avalue * intbase - 0.5); - if (diploid) { - avalue = pRandom->Normal(mean, sd); - if (avalue > 0.0) - pLoci[i].allele[1] = (int)(avalue * intbase + 0.5); - else - pLoci[i].allele[1] = (int)(avalue * intbase - 0.5); - } - } - -} - -// Set up specified locus at simulation initialisation -void Chromosome::initialise(const short locus, const short posn, const int aval) -{ - // note that initialising value is ADDED to current value to allow for pleiotropy - pLoci[locus].allele[posn] += aval; -} - -// Inherit from specified parent -void Chromosome::inherit(const Chromosome* parentChr, const short posn, const short nloc, - const double probmutn, const double probcross, const double mutnSD, const bool diploid) -{ - // NOTE: At present for diploid genome, presence of crossover is determined at each - // locus (except first). However, Roslyn has shown that it is more efficient to sample - // crossover locations from geometric distribution if number of loci is large. - // HOW LARGE IS 'LARGE' IN THIS CASE?... - - int ix = 0; // indexes maternal and paternal strands - if (diploid) ix = pRandom->Bernoulli(0.5); // start index at random - for (int i = 0; i < nloc; i++) { - if (diploid) { - pLoci[i].allele[posn] = parentChr->pLoci[i].allele[ix]; - if (pRandom->Bernoulli(probcross)) { // crossover occurs - if (ix == 0) ix = 1; else ix = 0; - } - } - else - pLoci[i].allele[posn] = parentChr->pLoci[i].allele[0]; - if (pRandom->Bernoulli(probmutn)) { // mutation occurs - double intbase = INTBASE; -#if RSDEBUG - int oldval = pLoci[i].allele[posn]; -#endif - double mutnvalue = pRandom->Normal(0, mutnSD); - if (mutnvalue > 0.0) - pLoci[i].allele[posn] += (int)(intbase * mutnvalue + 0.5); - else - pLoci[i].allele[posn] += (int)(intbase * mutnvalue - 0.5); -#if RSDEBUG - MUTNLOG << mutnvalue << " " << oldval << " " << pLoci[i].allele[posn] << " " << endl; -#endif - } - } -} - - -//--------------------------------------------------------------------------- - -// NB THIS FUNCTION IS CURRENTLY NOT BEING CALLED TO CONSTRUCT AN INSTANCE OF Genome -// Genome(int) IS USED INSTEAD - -Genome::Genome() { - pChromosome = NULL; - nChromosomes = 0; -} - -// Set up new genome at initialisation for 1 chromosome per trait -Genome::Genome(int nchromosomes, int nloci, bool d) { - - diploid = d; - if (nchromosomes > 0) nChromosomes = nchromosomes; else nChromosomes = 1; - pChromosome = new Chromosome * [nChromosomes]; - for (int i = 0; i < nChromosomes; i++) { - pChromosome[i] = new Chromosome(nloci); - } - -} - -// Set up new genome at initialisation for trait mapping -Genome::Genome(Species* pSpecies) { - int nloci; - nChromosomes = pSpecies->getNChromosomes(); - diploid = pSpecies->isDiploid(); - pChromosome = new Chromosome * [nChromosomes]; - for (int i = 0; i < nChromosomes; i++) { - nloci = pSpecies->getNLoci(i); - pChromosome[i] = new Chromosome(nloci); - } -} - -// Inherit genome from parent(s) -Genome::Genome(Species* pSpecies, Genome* mother, Genome* father) -{ - genomeData gen = pSpecies->getGenomeData(); - - nChromosomes = mother->nChromosomes; - diploid = mother->diploid; - pChromosome = new Chromosome * [nChromosomes]; - - for (int i = 0; i < nChromosomes; i++) { - pChromosome[i] = new Chromosome(mother->pChromosome[i]->nLoci()); - inherit(mother, 0, i, gen.probMutn, gen.probCrossover, gen.mutationSD); - if (diploid) { - if (father == 0) { // species is hermaphrodite - inherit again from mother - inherit(mother, 1, i, gen.probMutn, gen.probCrossover, gen.mutationSD); - } - else inherit(father, 1, i, gen.probMutn, gen.probCrossover, gen.mutationSD); - } - } - -} - -Genome::~Genome() { - - if (pChromosome == NULL) return; - - for (int i = 0; i < nChromosomes; i++) { - delete pChromosome[i]; - } - delete[] pChromosome; - -} - -//--------------------------------------------------------------------------- - -void Genome::setDiploid(bool dip) { diploid = dip; } -bool Genome::isDiploid(void) { return diploid; } -short Genome::getNChromosomes(void) { return nChromosomes; } - -//--------------------------------------------------------------------------- - -// Inherit from specified parent -void Genome::inherit(const Genome* parent, const short posn, const short chr, - const double probmutn, const double probcross, const double mutnSD) -{ - pChromosome[chr]->inherit(parent->pChromosome[chr], posn, parent->pChromosome[chr]->nLoci(), - probmutn, probcross, mutnSD, diploid); - -} - -void Genome::outGenHeaders(const int rep, const int landNr, const bool xtab) -{ - - if (landNr == -999) { // close file - if (outGenetic.is_open()) { - outGenetic.close(); outGenetic.clear(); - } - return; - } - - string name; - simParams sim = paramsSim->getSim(); - - if (sim.batchMode) { - name = paramsSim->getDir(2) - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) - + "_Land" + Int2Str(landNr) + "_Rep" + Int2Str(rep) + "_Genetics.txt"; - } - else { - name = paramsSim->getDir(2) + "Sim" + Int2Str(sim.simulation) - + "_Rep" + Int2Str(rep) + "_Genetics.txt"; - } - outGenetic.open(name.c_str()); - - outGenetic << "Rep\tYear\tSpecies\tIndID"; - if (xtab) { - for (int i = 0; i < nChromosomes; i++) { - int nloci = pChromosome[i]->nLoci(); - for (int j = 0; j < nloci; j++) { - outGenetic << "\tChr" << i << "Loc" << j << "Allele0"; - if (diploid) outGenetic << "\tChr" << i << "Loc" << j << "Allele1"; - } - } - outGenetic << endl; - } - else { - outGenetic << "\tChromosome\tLocus\tAllele0"; - if (diploid) outGenetic << "\tAllele1"; - outGenetic << endl; - } - -} - -void Genome::outGenetics(const int rep, const int year, const int spnum, - const int indID, const bool xtab) -{ - locus l; - if (xtab) { - outGenetic << rep << "\t" << year << "\t" << spnum << "\t" << indID; - for (int i = 0; i < nChromosomes; i++) { - int nloci = pChromosome[i]->nLoci(); - for (int j = 0; j < nloci; j++) { - l = pChromosome[i]->alleles(j); - outGenetic << "\t" << l.allele[0]; - if (diploid) outGenetic << "\t" << l.allele[1]; - } - } - outGenetic << endl; - } - else { - for (int i = 0; i < nChromosomes; i++) { - int nloci = pChromosome[i]->nLoci(); - for (int j = 0; j < nloci; j++) { - outGenetic << rep << "\t" << year << "\t" << spnum << "\t" - << indID << "\t" << i << "\t" << j; - l = pChromosome[i]->alleles(j); - outGenetic << "\t" << l.allele[0]; - if (diploid) outGenetic << "\t" << l.allele[1]; - outGenetic << endl; - } - } - } -} - -//--------------------------------------------------------------------------- - -// Set up new gene at initialisation for 1 chromosome per trait -void Genome::setGene(const short chr, const short exp, - const double traitval, const double alleleSD) - // NB PARAMETER exp FOR EXPRESSION TYPE IS NOT CURRENTLY USED... -{ - if (chr >= 0 && chr < nChromosomes) { - pChromosome[chr]->initialise(traitval, alleleSD, diploid); - } -} - -// Set up trait at initialisation for trait mapping -void Genome::setTrait(Species* pSpecies, const int trait, - const double traitval, const double alleleSD) -{ - traitAllele allele; - int nalleles = pSpecies->getNTraitAlleles(trait); - int ntraitmaps = pSpecies->getNTraitMaps(); - - int avalue; - double intbase = INTBASE; - if (trait < ntraitmaps) { - for (int i = 0; i < nalleles; i++) { - allele = pSpecies->getTraitAllele(trait, i); - avalue = (int)(pRandom->Normal(traitval, alleleSD) * intbase); - pChromosome[allele.chromo]->initialise(allele.locus, 0, avalue); - if (diploid) { - avalue = (int)(pRandom->Normal(traitval, alleleSD) * intbase); - pChromosome[allele.chromo]->initialise(allele.locus, 1, avalue); - } - } - } - else { // insufficient traits were defined - // alleles cannot be initialised - all individuals have mean phenotype - } - -} - -// Set up trait at initialisation for trait mapping -void Genome::setNeutralLoci(Species* pSpecies, const double alleleSD) -{ - traitAllele allele; - int nneutral = pSpecies->getNNeutralLoci(); - - double avalue; - double intbase = INTBASE; - for (int i = 0; i < nneutral; i++) { - allele = pSpecies->getNeutralAllele(i); - avalue = pRandom->Normal(0.0, alleleSD); - if (avalue > 0.0) - pChromosome[allele.chromo]->initialise(allele.locus, 0, (int)(avalue * intbase + 0.5)); - else - pChromosome[allele.chromo]->initialise(allele.locus, 0, (int)(avalue * intbase - 0.5)); - if (diploid) { - avalue = pRandom->Normal(0.0, alleleSD); - if (avalue > 0.0) - pChromosome[allele.chromo]->initialise(allele.locus, 1, (int)(avalue * intbase + 0.5)); - else - pChromosome[allele.chromo]->initialise(allele.locus, 1, (int)(avalue * intbase - 0.5)); - } - } -} - -// Return the expressed value of a gene when species has one chromosome per trait -double Genome::express(short chr, short expr, short indsex) -{ - double genevalue = 0.0; - genevalue = pChromosome[chr]->meanvalue(diploid); - return genevalue; -} - -// Return the expressed value of a trait when genetic architecture is defined -double Genome::express(Species* pSpecies, short traitnum) -{ - double genevalue = 0.0; - - traitAllele allele; - int nalleles = pSpecies->getNTraitAlleles(traitnum); - if (nalleles > 0) { - for (int i = 0; i < nalleles; i++) { - allele = pSpecies->getTraitAllele(traitnum, i); - genevalue += pChromosome[allele.chromo]->additive(allele.locus, diploid); - } - genevalue /= (double)nalleles; - if (diploid) genevalue /= 2.0; - } - return genevalue; -} - - -locusOK Genome::getAlleles(short chr, short loc) { - locusOK l; - l.allele[0] = l.allele[1] = 0; l.ok = false; - if (chr >= 0 && chr < nChromosomes) { - if (pChromosome[chr] != 0) { - if (loc >= 0 && loc < pChromosome[chr]->nLoci()) { - locus a = pChromosome[chr]->alleles(loc); - l.allele[0] = a.allele[0]; l.allele[1] = a.allele[1]; l.ok = true; - } - } - } - - return l; -} - -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- - diff --git a/Genome.h b/Genome.h deleted file mode 100644 index 4f01ede..0000000 --- a/Genome.h +++ /dev/null @@ -1,173 +0,0 @@ -/*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * - * This file is part of RangeShifter. - * - * RangeShifter 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. - * - * RangeShifter 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 RangeShifter. If not, see . - * - --------------------------------------------------------------------------*/ - -#ifndef GenomeH -#define GenomeH - -#include -#include - -#include "Parameters.h" -#include "Species.h" - -#define INTBASE 100.0; // to convert integer alleles into continuous traits - -struct locus { short allele[2]; }; -struct locusOK { short allele[2]; bool ok; }; - -//--------------------------------------------------------------------------- - -class Chromosome { - -public: - Chromosome(int); - ~Chromosome(); - short nLoci(void); - double additive( // Return trait value on normalised genetic scale - const bool // diploid - ); - double meanvalue( // Return trait value on normalised genetic scale - const bool // diploid - ); - double additive( // Return trait value on normalised genetic scale - const short, // locus - const bool // diploid - ); - locus alleles( // Return allele values at a specified locus - const int // position of locus on chromosome - ); - void initialise( // Set up chromosome at simulation initialisation - const double, // normalised phenotypic trait value - const double, // s.d. of allelic variance (genetic scale) - const bool // diploid - ); - void initialise( // Set up specified locus at simulation initialisation - const short, // locus - const short, // position: 0 from mother, 1 from father - const int // allele value - ); - void inherit( // Inherit chromosome from specified parent - const Chromosome*, // pointer to parent's chromosome - const short, // position: 0 from mother, 1 from father - const short, // no. of loci - const double, // mutation probability - const double, // crossover probability - const double, // s.d. of mutation magnitude (genetic scale) - const bool // diploid - ); - -protected: - -private: - short nloci; - locus* pLoci; - -}; - -//--------------------------------------------------------------------------- - -class Genome { - -public: - Genome(); - Genome(int, int, bool); - Genome(Species*); - Genome(Species*, Genome*, Genome*); - ~Genome(); - void setGene( // Set up new gene at initialisation for 1 chromosome per trait - const short, // chromosome number - const short, // expression type (NOT CURRENTLY USED) - const double, // normalised trait value - const double // s.d. of allelic variance - ); - void setTrait( // Set up trait at initialisation for trait mapping - Species*, // pointer to Species - const int, // trait number - const double, // normalised trait value - const double // s.d. of allelic variance - ); - void setNeutralLoci( // Set up neutral loci at initialisation - Species*, // pointer to Species - const double // s.d. of allelic variance - ); - double express( - // Return the expressed value of a gene when species has one chromosome per trait - short, // chromosome number - short, // expression type (NOT CURRENTLY USED) - short // individual's sex (NOT CURRENTLY USED) - ); - double express( - // Return the expressed value of a trait when genetic architecture is defined - Species*, // pointer to Species - short // trait number - ); - locusOK getAlleles( // Get allele values at a specified locus - short, // chromosome number - short // locus position on chromosome - ); - // SCFP NEW DECLARATIONS - void setDiploid(bool); - bool isDiploid(void); - void inherit( // Inherit from specified parent - const Genome*, // pointer to parent's genome - const short, // position: 0 from mother, 1 from father - const short, // chromasome number - const double, // mutation probability - const double, // crossover probability - const double // s.d. of mutation magnitude (genetic scale) - ); - short getNChromosomes(void); - void outGenHeaders( - const int, // replicate - const int, // landscape number - const bool // output as cross table? - ); - void outGenetics( - const int, // replicate - const int, // year - const int, // species number - const int, // individual ID - const bool // output as cross table? - ); - - -private: - short nChromosomes; // no. of chromosomes - bool diploid; - Chromosome** pChromosome; - -}; - -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- - -extern paramSim* paramsSim; -extern RSrandom* pRandom; - -#if RSDEBUG -extern ofstream DEBUGLOG; -extern ofstream MUTNLOG; -extern void DebugGUI(string); -#endif - -//--------------------------------------------------------------------------- - -#endif diff --git a/Individual.cpp b/Individual.cpp index 73f6f6f..b24d249 100644 --- a/Individual.cpp +++ b/Individual.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -16,29 +16,105 @@ * * You should have received a copy of the GNU General Public License * along with RangeShifter. If not, see . - * --------------------------------------------------------------------------*/ + //--------------------------------------------------------------------------- #include "Individual.h" + +#ifdef _OPENMP +#include +#endif + +//--------------------------------------------------------------------------- + +template +MemoryQueue::MemoryQueue(std::size_t size): + space(size), + data(new T[size]), + begin_idx(0), + nb_elts(0) +{ } + +template +T &MemoryQueue::front() { + return data[begin_idx]; +} + +template +T const &MemoryQueue::front() const { + return data[begin_idx]; +} + +template +T &MemoryQueue::back() { + return data[(begin_idx + nb_elts - 1) % space]; +} + +template +T const &MemoryQueue::back() const { + return data[(begin_idx + nb_elts - 1) % space]; +} + +template +void MemoryQueue::push(const T& value) { + size_t end_idx = (begin_idx + nb_elts) % space; + data[end_idx] = value; + nb_elts++; +} + +template +void MemoryQueue::push(T&& value) { + size_t end_idx = (begin_idx + nb_elts) % space; + data[end_idx] = std::move(value); + nb_elts++; +} + +template +void MemoryQueue::pop() { + begin_idx++; + begin_idx %= space; + nb_elts--; +} + +template +std::size_t MemoryQueue::size() const { + return nb_elts; +} + +template +bool MemoryQueue::empty() const { + return nb_elts == 0; +} + +template +bool MemoryQueue::full() const { + return nb_elts == space; +} + //--------------------------------------------------------------------------- +#ifdef _OPENMP +std::atomic Individual::indCounter = 0; +#else // _OPENMP int Individual::indCounter = 0; +#endif // _OPENMP +TraitFactory Individual::traitFactory = TraitFactory(); //--------------------------------------------------------------------------- // Individual constructor -Individual::Individual(Cell* pCell, Patch* pPatch, short stg, short a, short repInt, - float probmale, bool movt, short moveType) +Individual::Individual(Species* pSpecies, Cell* pCell, Patch* pPatch, short stg, short a, short repInt, + float probmale, bool movt, short moveType): + memory(pSpecies->getSpSMSTraits().memSize) { - indId = indCounter; - indCounter++; // unique identifier for each individual - + indId = indCounter; indCounter++; // unique identifier for each individual + geneticFitness = 1.0; stage = stg; - if (probmale <= 0.0) sex = 0; - else sex = pRandom->Bernoulli(probmale); + if (probmale <= 0.0) sex = FEM; + else sex = pRandom->Bernoulli(probmale) ? MAL : FEM; age = a; status = 0; @@ -49,6 +125,7 @@ Individual::Individual(Cell* pCell, Patch* pPatch, short stg, short a, short rep isDeveloping = false; pPrevCell = pCurrCell = pCell; pNatalPatch = pPatch; + pTrfrData = nullptr; //set to null as default if (movt) { locn loc = pCell->getLocn(); path = new pathData; @@ -59,354 +136,257 @@ Individual::Individual(Cell* pCell, Patch* pPatch, short stg, short a, short rep #endif if (moveType == 1) { // SMS // set up location data for SMS - smsData = new smsdata; - smsData->dp = smsData->gb = smsData->alphaDB = 1.0; - smsData->betaDB = 1; - smsData->prev.x = loc.x; - smsData->prev.y = loc.y; // previous location - smsData->goal.x = loc.x; - smsData->goal.y = loc.y; // goal location - initialised for dispersal bias + pTrfrData = make_unique(loc, loc); + } - else smsData = 0; if (moveType == 2) { // CRW // set up continuous co-ordinates etc. for CRW movement - crw = new crwParams; - crw->xc = ((float)pRandom->Random() * 0.999f) + (float)loc.x; - crw->yc = (float)(pRandom->Random() * 0.999f) + (float)loc.y; - crw->prevdrn = (float)(pRandom->Random() * 2.0 * PI); - crw->stepL = crw->rho = 0.0; + float xc = ((float)pRandom->Random() * 0.999f) + (float)loc.x; + float yc = (float)(pRandom->Random() * 0.999f) + (float)loc.y; + float prevdrn = (float)(pRandom->Random() * 2.0 * PI); + pTrfrData = make_unique(prevdrn, xc, yc); } - else crw = 0; } else { - path = 0; crw = 0; smsData = 0; + path = 0; + pTrfrData = make_unique(0.0, 0.0, 0.0); } - emigtraits = 0; - kerntraits = 0; - setttraits = 0; - pGenome = 0; } Individual::~Individual(void) { if (path != 0) delete path; - if (crw != 0) delete crw; - if (smsData != 0) delete smsData; - if (emigtraits != 0) delete emigtraits; - if (kerntraits != 0) delete kerntraits; - if (setttraits != 0) delete setttraits; +} - if (pGenome != 0) delete pGenome; +void Individual::setEmigTraits(const emigTraits& emig) { + pEmigTraits = make_unique(emig); + } +void Individual::setSettleTraits(const settleTraits& settle) { + pSettleTraits = make_unique(settle); } -//--------------------------------------------------------------------------- +QuantitativeTrait* Individual::getTrait(TraitType trait) const { + auto p = this->spTraitTable.find(trait); + if (p == spTraitTable.end()) + throw runtime_error("Trait does not exist in trait table."); + else return p->second.get(); + } +set Individual::getTraitTypes() { + auto kv = std::views::keys(this->spTraitTable); + set< TraitType > keys{ kv.begin(), kv.end() }; + return keys; + } + +//--------------------------------------------------------------------------- +// Inheritance for diploid, sexual species //--------------------------------------------------------------------------- +void Individual::inherit(Species* pSpecies, const Individual* mother, const Individual* father) { -// Set genes for individual variation from species initialisation parameters -void Individual::setGenes(Species* pSpecies, int resol) { - demogrParams dem = pSpecies->getDemogr(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); - settleType sett = pSpecies->getSettle(); - genomeData gen = pSpecies->getGenomeData(); - simParams sim = paramsSim->getSim(); - int ntraits; // first trait for all/female expression, second for male expression + int events = 0; + const set chromosomeEnds = pSpecies->getChromosomeEnds(); + const int genomeSize = pSpecies->getGenomeSize(); - if (gen.trait1Chromosome) { - pGenome = new Genome(pSpecies->getNChromosomes(), pSpecies->getNLoci(0), - pSpecies->isDiploid()); - } - else { - pGenome = new Genome(pSpecies); - } + int maternalStartingChromosome = pRandom->Bernoulli(0.5); + int paternalStartingChromosome = pRandom->Bernoulli(0.5); - int gposn = 0; // current position on genome - int expr = 0; // gene expression type - NOT CURRENTLY USED + set maternalRecomPositions; + set paternalRecomPositions; - if (emig.indVar) { // set emigration genes - int emigposn = gposn; - double d0, alpha, beta; - emigParams eparams; - if (emig.sexDep) { // must be a sexual species - ntraits = 2; - } - else { - if (dem.repType == 0) { // asexual reproduction (haploid) - ntraits = 1; - } - else { // sexual reproduction - ntraits = 1; + // Determine which parental chromosomes are inherited + for (int pos : chromosomeEnds) { + if (pRandom->Bernoulli(0.5)) // switch strand for next chromosome + maternalRecomPositions.insert(pos); + if (pRandom->Bernoulli(0.5)) + paternalRecomPositions.insert(pos); } - } - for (int g = 0; g < ntraits; g++) { // first trait for females/all, second for males - eparams = pSpecies->getEmigParams(0, g); - d0 = pRandom->Normal(0.0, eparams.d0SD) / eparams.d0Scale; - if (emig.densDep) { - alpha = pRandom->Normal(0.0, eparams.alphaSD) / eparams.alphaScale; - beta = pRandom->Normal(0.0, eparams.betaSD) / eparams.betaScale; - } - if (gen.trait1Chromosome) { - pGenome->setGene(gposn++, expr, d0, gen.alleleSD); - if (emig.densDep) { - pGenome->setGene(gposn++, expr, alpha, gen.alleleSD); - pGenome->setGene(gposn++, expr, beta, gen.alleleSD); - } - } - else { - pGenome->setTrait(pSpecies, gposn++, d0, gen.alleleSD); - if (emig.densDep) { - pGenome->setTrait(pSpecies, gposn++, alpha, gen.alleleSD); - pGenome->setTrait(pSpecies, gposn++, beta, gen.alleleSD); - } - } - } - // record phenotypic traits - if (emig.densDep) { - setEmigTraits(pSpecies, emigposn, 3, emig.sexDep); - } - else { - setEmigTraits(pSpecies, emigposn, 1, emig.sexDep); - } - } - if (trfr.indVar) { // set transfer genes - int trfrposn = gposn; - if (trfr.sexDep) { // must be a sexual species - ntraits = 2; - } - else { - if (dem.repType == 0) { // asexual reproduction - ntraits = 1; - } - else { // sexual reproduction - ntraits = 1; - } + // Draw recombination events for maternal genome + if (pSpecies->getRecombinationRate() > 0.0) + events = pRandom->Binomial(genomeSize, pSpecies->getRecombinationRate()); + // if poisson exceeds genomeSize, bound to genomeSize + int nbrCrossOvers = events + maternalRecomPositions.size(); + if (nbrCrossOvers > genomeSize) { + nbrCrossOvers = genomeSize; } - if (trfr.moveModel) { - if (trfr.moveType == 1) { // set SMS genes - double dp, gb, alphaDB, betaDB; - trfrSMSParams smsparams = pSpecies->getSMSParams(0, 0); // only traits for females/all - trfrSMSTraits smstraits = pSpecies->getSMSTraits(); - dp = pRandom->Normal(0.0, smsparams.dpSD) / smsparams.dpScale; - gb = pRandom->Normal(0.0, smsparams.gbSD) / smsparams.gbScale; - if (smstraits.goalType == 2) { - alphaDB = pRandom->Normal(0.0, smsparams.alphaDBSD) / smsparams.alphaDBScale; - betaDB = pRandom->Normal(0.0, smsparams.betaDBSD) / smsparams.betaDBScale; - } - if (gen.trait1Chromosome) { - pGenome->setGene(gposn++, expr, dp, gen.alleleSD); - pGenome->setGene(gposn++, expr, gb, gen.alleleSD); - if (smstraits.goalType == 2) { - pGenome->setGene(gposn++, expr, alphaDB, gen.alleleSD); - pGenome->setGene(gposn++, expr, betaDB, gen.alleleSD); - } - } - else { - pGenome->setTrait(pSpecies, gposn++, dp, gen.alleleSD); - pGenome->setTrait(pSpecies, gposn++, gb, gen.alleleSD); - if (smstraits.goalType == 2) { - pGenome->setTrait(pSpecies, gposn++, alphaDB, gen.alleleSD); - pGenome->setTrait(pSpecies, gposn++, betaDB, gen.alleleSD); - } - } - // record phenotypic traits - if (smstraits.goalType == 2) - setSMSTraits(pSpecies, trfrposn, 4, false); - else - setSMSTraits(pSpecies, trfrposn, 2, false); + while (maternalRecomPositions.size() < nbrCrossOvers) { + // Sample recombination sites + maternalRecomPositions.insert(pRandom->IRandom(0, genomeSize)); } - if (trfr.moveType == 2) { // set CRW genes - double stepL, rho; - trfrCRWParams m = pSpecies->getCRWParams(0, 0); // only traits for females/all - stepL = pRandom->Normal(0.0, m.stepLgthSD) / m.stepLScale; - rho = pRandom->Normal(0.0, m.rhoSD) / m.rhoScale; - if (gen.trait1Chromosome) { - pGenome->setGene(gposn++, expr, stepL, gen.alleleSD); - pGenome->setGene(gposn++, expr, rho, gen.alleleSD); - } - else { - pGenome->setTrait(pSpecies, gposn++, stepL, gen.alleleSD); - pGenome->setTrait(pSpecies, gposn++, rho, gen.alleleSD); + + // Draw recombination events for paternal genome + if (pSpecies->getRecombinationRate() > 0.0) + events = pRandom->Binomial(genomeSize, pSpecies->getRecombinationRate()); + nbrCrossOvers = events + paternalRecomPositions.size(); + if (nbrCrossOvers > genomeSize) { + nbrCrossOvers = genomeSize; } - // record phenotypic traits - setCRWTraits(pSpecies, trfrposn, 2, false); + while (paternalRecomPositions.size() < nbrCrossOvers) { + paternalRecomPositions.insert(pRandom->IRandom(0, genomeSize)); } - } - else { // set kernel genes - double dist1, dist2, prob1; - trfrKernParams k; - for (int g = 0; g < ntraits; g++) { // first traits for females/all, second for males - k = pSpecies->getKernParams(0, g); - dist1 = pRandom->Normal(0.0, k.dist1SD) / k.dist1Scale; - if (trfr.twinKern) + + // Inherit genes for each trait + const auto& spTraits = pSpecies->getTraitTypes(); + for (auto const& trait : spTraits) { - dist2 = pRandom->Normal(0.0, k.dist2SD) / k.dist2Scale; - prob1 = pRandom->Normal(0.0, k.PKern1SD) / k.PKern1Scale; + const auto motherTrait = mother->getTrait(trait); + const auto fatherTrait = father->getTrait(trait); + auto newTrait = motherTrait->clone(); // shallow copy pointer to species-level attributes + + // Inherit from mother first + newTrait->inheritGenes(true, motherTrait, maternalRecomPositions, maternalStartingChromosome); + if (newTrait->isInherited()) { + // Inherit father trait values + newTrait->inheritGenes(false, fatherTrait, paternalRecomPositions, paternalStartingChromosome); + if (newTrait->getMutationRate() > 0 && pSpecies->areMutationsOn()) + newTrait->mutate(); } - if (gen.trait1Chromosome) { - pGenome->setGene(gposn++, expr, dist1, gen.alleleSD); - if (trfr.twinKern) - { - pGenome->setGene(gposn++, expr, dist2, gen.alleleSD); - pGenome->setGene(gposn++, expr, prob1, gen.alleleSD); + if (trait == GENETIC_LOAD1 || trait == GENETIC_LOAD2 || trait == GENETIC_LOAD3 || trait == GENETIC_LOAD4 || trait == GENETIC_LOAD5) + geneticFitness *= newTrait->express(); + + // Add the inherited trait and genes to the newborn's list + spTraitTable.insert(make_pair(trait, move(newTrait))); } } - else { - pGenome->setTrait(pSpecies, gposn++, dist1, gen.alleleSD); - if (trfr.twinKern) + +//--------------------------------------------------------------------------- +// Inheritance for haploid, asexual species +//--------------------------------------------------------------------------- +void Individual::inherit(Species* pSpecies, const Individual* mother) { + set recomPositions; //not used here cos haploid but need it for inherit function, not ideal + int startingChromosome = 0; + + const auto& spTraits = pSpecies->getTraitTypes(); + + for (auto const& trait : spTraits) { - pGenome->setTrait(pSpecies, gposn++, dist2, gen.alleleSD); - pGenome->setTrait(pSpecies, gposn++, prob1, gen.alleleSD); + const auto motherTrait = mother->getTrait(trait); + auto newTrait = motherTrait->clone(); // shallow copy, pointer to species trait initialised and empty sequence + + newTrait->inheritGenes(true, motherTrait, recomPositions, startingChromosome); + if (newTrait->isInherited()) { + if (newTrait->getMutationRate() > 0 && pSpecies->areMutationsOn()) + newTrait->mutate(); } + + if (trait == GENETIC_LOAD1 || trait == GENETIC_LOAD2 || trait == GENETIC_LOAD3 || trait == GENETIC_LOAD4 || trait == GENETIC_LOAD5) + geneticFitness *= newTrait->express(); + + // Add the inherited trait and genes to the newborn's list + spTraitTable.insert(make_pair(trait, move(newTrait))); } } - // record phenotypic traits - if (trfr.twinKern) + +// Initialise individual trait genes from species-level traits +void Individual::setUpGenes(Species* pSpecies, int resol) { + + // this way to keep spp trait table immutable i.e. not able to call getTraitTable, + // could pass it back by value (copy) instead but could be heavy if large map + const auto& traitTypes = pSpecies->getTraitTypes(); + for (auto const& traitType : traitTypes) { - setKernTraits(pSpecies, trfrposn, 3, resol, trfr.sexDep); + const auto spTrait = pSpecies->getSpTrait(traitType); + this->spTraitTable.emplace(traitType, traitFactory.Create(traitType, spTrait)); } - else { - setKernTraits(pSpecies, trfrposn, 1, resol, trfr.sexDep); + expressDispersalPhenotypes(pSpecies, resol); + expressGeneticLoad(pSpecies); } - } - } - if (sett.indVar) { - int settposn = gposn; - double s0, alpha, beta; - settParams sparams; - if (sett.sexDep) { // must be a sexual species - ntraits = 2; - } - else { - if (dem.repType == 0) { // asexual reproduction - ntraits = 1; - } - else { // sexual reproduction - ntraits = 1; - } - } - for (int g = 0; g < ntraits; g++) { // first trait for females/all, second for males - if (sim.batchMode) { - sparams = pSpecies->getSettParams(0, g); - } - else { // individual variability not (yet) implemented as sex-dependent in GUI - sparams = pSpecies->getSettParams(0, 0); - } - s0 = pRandom->Normal(0.0, sparams.s0SD) / sparams.s0Scale; - alpha = pRandom->Normal(0.0, sparams.alphaSSD) / sparams.alphaSScale; - beta = pRandom->Normal(0.0, sparams.betaSSD) / sparams.betaSScale; - - if (gen.trait1Chromosome) { - pGenome->setGene(gposn++, expr, s0, gen.alleleSD); - pGenome->setGene(gposn++, expr, alpha, gen.alleleSD); - pGenome->setGene(gposn++, expr, beta, gen.alleleSD); - } - else { - pGenome->setTrait(pSpecies, gposn++, s0, gen.alleleSD); - pGenome->setTrait(pSpecies, gposn++, alpha, gen.alleleSD); - pGenome->setTrait(pSpecies, gposn++, beta, gen.alleleSD); - } - } +void Individual::expressDispersalPhenotypes(Species* pSpecies, int resol) { + + const emigRules emig = pSpecies->getEmigRules(); + const transferRules trfr = pSpecies->getTransferRules(); + const settleType sett = pSpecies->getSettle(); + const settleRules settRules = pSpecies->getSettRules(stage, sex); + // record phenotypic traits - setSettTraits(pSpecies, settposn, 3, sett.sexDep); + if (emig.indVar) setEmigTraits(pSpecies, emig.sexDep, emig.densDep); + if (trfr.indVar) setTransferTraits(pSpecies, trfr, resol); + if (sett.indVar) setSettlementTraits(pSpecies, sett.sexDep, settRules.densDep); } - if (!gen.trait1Chromosome) { - if (gen.neutralMarkers || pSpecies->getNNeutralLoci() > 0) { - pGenome->setNeutralLoci(pSpecies, gen.alleleSD); - } +// Set the fitness attribute of individuals +// Only called at initialisation, otherwise probably faster to compute directly during inheritance +void Individual::expressGeneticLoad(Species* pSpecies) { + const int nbGenLoadTraits = pSpecies->getNbGenLoadTraits(); + const vector whichTrait = { GENETIC_LOAD1 , GENETIC_LOAD2, GENETIC_LOAD3, GENETIC_LOAD4, GENETIC_LOAD5 }; + for (int i = 0; i < nbGenLoadTraits; i++) { + if (spTraitTable.contains(whichTrait[i])) + geneticFitness *= getTrait(whichTrait[i])->express(); } } -// Inherit genome from parent(s) -void Individual::setGenes(Species* pSpecies, Individual* mother, Individual* father, - int resol) -{ - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); - settleType sett = pSpecies->getSettle(); - - Genome* pFatherGenome; - if (father == 0) pFatherGenome = 0; else pFatherGenome = father->pGenome; +void Individual::setTransferTraits(Species* pSpecies, transferRules trfr, int resol) { + if (trfr.usesMovtProc) { + if (trfr.moveType == 1) { + setIndSMSTraits(pSpecies); + } + else setIndCRWTraits(pSpecies); + } + else setIndKernelTraits(pSpecies, trfr.sexDep, trfr.twinKern, resol); +} - pGenome = new Genome(pSpecies, mother->pGenome, pFatherGenome); +void Individual::setSettlementTraits(Species* pSpecies, bool sexDep, bool densDep) { - if (emig.indVar) { - // record emigration traits - if (father == 0) { // haploid - if (emig.densDep) { - setEmigTraits(pSpecies, 0, 3, 0); - } - else { - setEmigTraits(pSpecies, 0, 1, 0); + settleTraits s; s.s0 = s.alpha = s.beta = 0.0; + if (sexDep) { + if (this->getSex() == MAL) { + s.s0 = getTrait(S_S0_M)->express(); + if (densDep) { + s.alpha = getTrait(S_ALPHA_M)->express(); + s.beta = getTrait(S_BETA_M)->express(); } } - else { // diploid - if (emig.densDep) { - setEmigTraits(pSpecies, 0, 3, emig.sexDep); - } - else { - setEmigTraits(pSpecies, 0, 1, emig.sexDep); + else if (this->getSex() == FEM) { + s.s0 = getTrait(S_S0_F)->express(); + if (densDep) { + s.alpha = getTrait(S_ALPHA_F)->express(); + s.beta = getTrait(S_BETA_F)->express(); } } + else { + throw runtime_error("Attempt to express invalid emigration trait sex."); + } + } + else { + s.s0 = getTrait(S_S0)->express(); + if (densDep) { + s.alpha = getTrait(S_ALPHA)->express(); + s.beta = getTrait(S_BETA)->express(); + } } - if (trfr.indVar) { - // record movement model traits - if (trfr.moveModel) { - if (trfr.moveType == 1) { // SMS - trfrSMSTraits s = pSpecies->getSMSTraits(); - if (s.goalType == 2) - setSMSTraits(pSpecies, trfr.movtTrait[0], 4, 0); - else - setSMSTraits(pSpecies, trfr.movtTrait[0], 2, 0); - } - if (trfr.moveType == 2) { // CRW - setCRWTraits(pSpecies, trfr.movtTrait[0], 2, 0); - } + pSettleTraits = make_unique(); + pSettleTraits->s0 = (float)(s.s0); + pSettleTraits->alpha = (float)(s.alpha); + pSettleTraits->beta = (float)(s.beta); + if (pSettleTraits->s0 < 0.0) pSettleTraits->s0 = 0.0; + if (pSettleTraits->s0 > 1.0) pSettleTraits->s0 = 1.0; + return; } - else { // kernel - if (father == 0) { // haploid - if (trfr.twinKern) + + +// Inherit genome from parent(s), diploid +void Individual::inheritTraits(Species* pSpecies, Individual* mother, Individual* father, int resol) { - setKernTraits(pSpecies, trfr.movtTrait[0], 3, resol, 0); + inherit(pSpecies, mother, father); + expressDispersalPhenotypes(pSpecies, resol); } - else { - setKernTraits(pSpecies, trfr.movtTrait[0], 1, resol, 0); - } - } - else { // diploid - if (trfr.twinKern) + +// Inherit genome from mother, haploid +void Individual::inheritTraits(Species* pSpecies, Individual* mother, int resol) { - setKernTraits(pSpecies, trfr.movtTrait[0], 3, resol, trfr.sexDep); + inherit(pSpecies, mother); + expressDispersalPhenotypes(pSpecies, resol); } - else { - setKernTraits(pSpecies, trfr.movtTrait[0], 1, resol, trfr.sexDep); - } - } - } - } - - if (sett.indVar) { - // record settlement traits - if (father == 0) { // haploid - setSettTraits(pSpecies, sett.settTrait[0], 3, 0); - } - else { // diploid - setSettTraits(pSpecies, sett.settTrait[0], 3, sett.sexDep); - } - } -} //--------------------------------------------------------------------------- // Identify whether an individual is a potentially breeding female - // if so, return her stage, otherwise return 0 int Individual::breedingFem(void) { - if (sex == 0) { - if (status == 0 || status == 4 || status == 5) return stage; + if (sex == FEM) { + if (status == 0 || status == 4 || status == 5 || status == 10) return stage; else return 0; } else return 0; @@ -414,10 +394,17 @@ int Individual::breedingFem(void) { int Individual::getId(void) { return indId; } -int Individual::getSex(void) { return sex; } +sex_t Individual::getSex(void) { return sex; } int Individual::getStatus(void) { return status; } +float Individual::getGeneticFitness(void) { return geneticFitness; } + +bool Individual::isViable() const { + float probViability = geneticFitness > 1.0 ? 1.0 : geneticFitness; + return probViability >= pRandom->Random(); +} + indStats Individual::getStats(void) { indStats s; s.stage = stage; s.sex = sex; s.age = age; s.status = status; s.fallow = fallow; @@ -425,13 +412,12 @@ indStats Individual::getStats(void) { return s; } -Cell* Individual::getLocn(const short option) { - if (option == 0) { // return previous location - return pPrevCell; - } - else { // return current location - return pCurrCell; - } +Cell* Individual::getPrevCell() { + return pPrevCell; +} + +Cell* Individual::getCurrCell() { + return pCurrCell; } Patch* Individual::getNatalPatch(void) { return pNatalPatch; } @@ -477,340 +463,218 @@ void Individual::setSettPatch(const settlePatch s) { path->pSettPatch = s.pSettPatch; } -// Set phenotypic emigration traits -void Individual::setEmigTraits(Species* pSpecies, short emiggenelocn, short nemigtraits, - bool sexdep) { - emigTraits e; e.d0 = e.alpha = e.beta = 0.0; - if (pGenome != 0) { - if (pSpecies->has1ChromPerTrait()) { - if (sexdep) { - if (nemigtraits == 3) { // emigration is density-dependent - e.d0 = (float)pGenome->express(emiggenelocn + 3 * sex, 0, 0); - e.alpha = (float)pGenome->express(emiggenelocn + 3 * sex + 1, 0, 0); - e.beta = (float)pGenome->express(emiggenelocn + 3 * sex + 2, 0, 0); +void Individual::setEmigTraits(Species* pSpecies, bool sexDep, bool densityDep) { + emigTraits e; + e.d0 = e.alpha = e.beta = 0.0; + if (sexDep) { + if (this->getSex() == MAL) { + e.d0 = this->getTrait(E_D0_M)->express(); + if (densityDep) { + e.alpha = getTrait(E_ALPHA_M)->express(); + e.beta = getTrait(E_BETA_M)->express(); } - else { - e.d0 = (float)pGenome->express(emiggenelocn + sex, 0, 0); } + else if (this->getSex() == FEM) { + e.d0 = this->getTrait(E_D0_F)->express(); + if (densityDep) { + e.alpha = getTrait(E_ALPHA_F)->express(); + e.beta = getTrait(E_BETA_F)->express(); } - else { - e.d0 = (float)pGenome->express(emiggenelocn, 0, 0); - if (nemigtraits == 3) { // emigration is density-dependent - e.alpha = (float)pGenome->express(emiggenelocn + 1, 0, 0); - e.beta = (float)pGenome->express(emiggenelocn + 2, 0, 0); } - } - } else { - if (sexdep) { - if (nemigtraits == 3) { // emigration is density-dependent - e.d0 = (float)pGenome->express(pSpecies, emiggenelocn + 3 * sex); - e.alpha = (float)pGenome->express(pSpecies, emiggenelocn + 3 * sex + 1); - e.beta = (float)pGenome->express(pSpecies, emiggenelocn + 3 * sex + 2); + throw runtime_error("Attempt to express invalid emigration trait sex."); } - else { - e.d0 = (float)pGenome->express(pSpecies, emiggenelocn + sex); } - } else { - e.d0 = (float)pGenome->express(pSpecies, emiggenelocn); - if (nemigtraits == 3) { // emigration is density-dependent - e.alpha = (float)pGenome->express(pSpecies, emiggenelocn + 1); - e.beta = (float)pGenome->express(pSpecies, emiggenelocn + 2); + e.d0 = this->getTrait(E_D0)->express(); + if (densityDep) { + e.alpha = getTrait(E_ALPHA)->express(); + e.beta = getTrait(E_BETA)->express(); } } - } - } - emigParams eparams; - if (sexdep) { - eparams = pSpecies->getEmigParams(0, sex); - } - else { - eparams = pSpecies->getEmigParams(0, 0); - } - emigtraits = new emigTraits; - emigtraits->d0 = (float)(e.d0 * eparams.d0Scale + eparams.d0Mean); - emigtraits->alpha = (float)(e.alpha * eparams.alphaScale + eparams.alphaMean); - emigtraits->beta = (float)(e.beta * eparams.betaScale + eparams.betaMean); - if (emigtraits->d0 < 0.0) emigtraits->d0 = 0.0; - if (emigtraits->d0 > 1.0) emigtraits->d0 = 1.0; + pEmigTraits = make_unique(); + pEmigTraits->d0 = e.d0; + pEmigTraits->alpha = e.alpha; + pEmigTraits->beta = e.beta; + + // Below must never trigger, phenotype is bounded in express() + if (pEmigTraits->d0 < 0.0) throw runtime_error("d0 value has become negative."); + if (pEmigTraits->d0 > 1.0) throw runtime_error("d0 value has exceeded 1."); return; } // Get phenotypic emigration traits -emigTraits Individual::getEmigTraits(void) { - emigTraits e; e.d0 = e.alpha = e.beta = 0.0; - if (emigtraits != 0) { - e.d0 = emigtraits->d0; - e.alpha = emigtraits->alpha; - e.beta = emigtraits->beta; +emigTraits Individual::getIndEmigTraits(void) { + emigTraits e; + e.d0 = e.alpha = e.beta = 0.0; + if (pEmigTraits != 0) { + e.d0 = pEmigTraits->d0; + e.alpha = pEmigTraits->alpha; + e.beta = pEmigTraits->beta; } return e; } - // Set phenotypic transfer by kernel traits -void Individual::setKernTraits(Species* pSpecies, short kerngenelocn, short nkerntraits, - int resol, bool sexdep) { - trfrKernTraits k; k.meanDist1 = k.meanDist2 = k.probKern1 = 0.0; - if (pGenome != 0) { - if (pSpecies->has1ChromPerTrait()) { - if (sexdep) { - if (nkerntraits == 3) { // twin kernel - k.meanDist1 = (float)pGenome->express(kerngenelocn + 3 * sex, 0, sex); - k.meanDist2 = (float)pGenome->express(kerngenelocn + 3 * sex + 1, 0, sex); - k.probKern1 = (float)pGenome->express(kerngenelocn + 3 * sex + 2, 0, sex); +void Individual::setIndKernelTraits(Species* pSpecies, bool sexDep, bool twinKernel, int resol) { + + trfrKernelParams k; + k.meanDist1 = k.meanDist2 = k.probKern1 = 0.0; + + if (sexDep) { + if (this->sex == MAL) { + k.meanDist1 = getTrait(KERNEL_MEANDIST_1_M)->express(); + + if (twinKernel) { // twin kernel + k.meanDist2 = getTrait(KERNEL_MEANDIST_2_M)->express(); + k.probKern1 = getTrait(KERNEL_PROBABILITY_M)->express(); } - else { - k.meanDist1 = (float)pGenome->express(kerngenelocn + sex, 0, sex); } + else if (this->sex == FEM) { + k.meanDist1 = getTrait(KERNEL_MEANDIST_1_F)->express(); + + if (twinKernel) { // twin kernel + k.meanDist2 = getTrait(KERNEL_MEANDIST_2_F)->express(); + k.probKern1 = getTrait(KERNEL_PROBABILITY_F)->express(); } - else { - k.meanDist1 = (float)pGenome->express(kerngenelocn, 0, 0); - if (nkerntraits == 3) { // twin kernel - k.meanDist2 = (float)pGenome->express(kerngenelocn + 1, 0, 0); - k.probKern1 = (float)pGenome->express(kerngenelocn + 2, 0, 0); } - } - } else { - if (sexdep) { - if (nkerntraits == 3) { // twin kernel - k.meanDist1 = (float)pGenome->express(pSpecies, kerngenelocn + 3 * sex); - k.meanDist2 = (float)pGenome->express(pSpecies, kerngenelocn + 3 * sex + 1); - k.probKern1 = (float)pGenome->express(pSpecies, kerngenelocn + 3 * sex + 2); + throw runtime_error("Attempt to express invalid kernel transfer trait sex."); } - else { - k.meanDist1 = (float)pGenome->express(pSpecies, kerngenelocn + sex); } - } else { - k.meanDist1 = (float)pGenome->express(pSpecies, kerngenelocn); - if (nkerntraits == 3) { // twin kernel - k.meanDist2 = (float)pGenome->express(pSpecies, kerngenelocn + 1); - k.probKern1 = (float)pGenome->express(pSpecies, kerngenelocn + 2); + k.meanDist1 = getTrait(KERNEL_MEANDIST_1)->express(); + + if (twinKernel) { // twin kernel + k.meanDist2 = getTrait(KERNEL_MEANDIST_2)->express(); + k.probKern1 = getTrait(KERNEL_PROBABILITY)->express(); } } - } - } - trfrKernParams kparams; - if (sexdep) { - kparams = pSpecies->getKernParams(0, sex); - } - else { - kparams = pSpecies->getKernParams(0, 0); - } - kerntraits = new trfrKernTraits; - kerntraits->meanDist1 = (float)(k.meanDist1 * kparams.dist1Scale + kparams.dist1Mean); - kerntraits->meanDist2 = (float)(k.meanDist2 * kparams.dist2Scale + kparams.dist2Mean); - kerntraits->probKern1 = (float)(k.probKern1 * kparams.PKern1Scale + kparams.PKern1Mean); + float meanDist1 = (float)(k.meanDist1); + float meanDist2 = (float)(k.meanDist2); + float probKern1 = (float)(k.probKern1); + if (!pSpecies->useFullKernel()) { // kernel mean(s) may not be less than landscape resolution - if (kerntraits->meanDist1 < resol) kerntraits->meanDist1 = (float)resol; - if (kerntraits->meanDist2 < resol) kerntraits->meanDist2 = (float)resol; + if (meanDist1 < resol) meanDist1 = (float)resol; + if (meanDist2 < resol) meanDist2 = (float)resol; } - if (kerntraits->probKern1 < 0.0) kerntraits->probKern1 = 0.0; - if (kerntraits->probKern1 > 1.0) kerntraits->probKern1 = 1.0; + if (probKern1 < 0.0) probKern1 = 0.0; + if (probKern1 > 1.0) probKern1 = 1.0; + auto& pKernel = dynamic_cast(*pTrfrData); + pKernel.meanDist1 = meanDist1; + pKernel.meanDist2 = meanDist2; + pKernel.probKern1 = probKern1; + return; } + + // Get phenotypic emigration traits -trfrKernTraits Individual::getKernTraits(void) { - trfrKernTraits k; k.meanDist1 = k.meanDist2 = k.probKern1 = 0.0; - if (kerntraits != 0) { - k.meanDist1 = kerntraits->meanDist1; - k.meanDist2 = kerntraits->meanDist2; - k.probKern1 = kerntraits->probKern1; +trfrKernelParams Individual::getIndKernTraits(void) { + trfrKernelParams k; k.meanDist1 = k.meanDist2 = k.probKern1 = 0.0; + if (pTrfrData != 0) { + + auto& pKernel = dynamic_cast(*pTrfrData); + + k.meanDist1 = pKernel.meanDist1; + k.meanDist2 = pKernel.meanDist2; + k.probKern1 = pKernel.probKern1; } + return k; } -// Set phenotypic transfer by SMS traits -void Individual::setSMSTraits(Species* pSpecies, short SMSgenelocn, short nSMStraits, - bool sexdep) { - trfrSMSTraits s = pSpecies->getSMSTraits(); +void Individual::setIndSMSTraits(Species* pSpecies) { + + trfrSMSTraits s = pSpecies->getSpSMSTraits(); + double dp, gb, alphaDB, betaDB; dp = gb = alphaDB = betaDB = 0.0; - if (pGenome != 0) { - if (pSpecies->has1ChromPerTrait()) { - if (sexdep) { - dp = pGenome->express(SMSgenelocn, 0, 0); - gb = pGenome->express(SMSgenelocn + 1, 0, 0); - if (nSMStraits == 4) { - alphaDB = pGenome->express(SMSgenelocn + 2, 0, 0); - betaDB = pGenome->express(SMSgenelocn + 3, 0, 0); - } - } - else { - dp = pGenome->express(SMSgenelocn, 0, 0); - gb = pGenome->express(SMSgenelocn + 1, 0, 0); - if (nSMStraits == 4) { - alphaDB = pGenome->express(SMSgenelocn + 2, 0, 0); - betaDB = pGenome->express(SMSgenelocn + 3, 0, 0); - } - } - } - else { - if (sexdep) { - dp = pGenome->express(pSpecies, SMSgenelocn); - gb = pGenome->express(pSpecies, SMSgenelocn + 1); - if (nSMStraits == 4) { - alphaDB = pGenome->express(pSpecies, SMSgenelocn + 2); - betaDB = pGenome->express(pSpecies, SMSgenelocn + 3); - } - } - else { - dp = pGenome->express(pSpecies, SMSgenelocn); - gb = pGenome->express(pSpecies, SMSgenelocn + 1); - if (nSMStraits == 4) { - alphaDB = pGenome->express(pSpecies, SMSgenelocn + 2); - betaDB = pGenome->express(pSpecies, SMSgenelocn + 3); + dp = getTrait(SMS_DP)->express(); + if (s.goalType == 2) { + gb = getTrait(SMS_GB)->express(); + alphaDB = getTrait(SMS_ALPHADB)->express(); + betaDB = getTrait(SMS_BETADB)->express(); } - } - } - } - trfrSMSParams smsparams; - if (sexdep) { - smsparams = pSpecies->getSMSParams(0, 0); - } - else { - smsparams = pSpecies->getSMSParams(0, 0); - } - smsData->dp = (float)(dp * smsparams.dpScale + smsparams.dpMean); - smsData->gb = (float)(gb * smsparams.gbScale + smsparams.gbMean); + + auto& pSMS = dynamic_cast(*pTrfrData); + pSMS.dp = (float)(dp); + pSMS.gb = (float)(gb); if (s.goalType == 2) { - smsData->alphaDB = (float)(alphaDB * smsparams.alphaDBScale + smsparams.alphaDBMean); - smsData->betaDB = (int)(betaDB * smsparams.betaDBScale + smsparams.betaDBMean + 0.5); + pSMS.alphaDB = (float)(alphaDB); + pSMS.betaDB = (int)(betaDB); } else { - smsData->alphaDB = s.alphaDB; - smsData->betaDB = s.betaDB; + pSMS.alphaDB = s.alphaDB; + pSMS.betaDB = s.betaDB; } - if (smsData->dp < 1.0) smsData->dp = 1.0; - if (smsData->gb < 1.0) smsData->gb = 1.0; - if (smsData->alphaDB <= 0.0) smsData->alphaDB = 0.000001f; - if (smsData->betaDB < 1) smsData->betaDB = 1; + if (pSMS.dp < 1.0) pSMS.dp = 1.0; + if (pSMS.gb < 1.0) pSMS.gb = 1.0; + if (pSMS.alphaDB <= 0.0) pSMS.alphaDB = 0.000001f; + if (pSMS.betaDB < 1) pSMS.betaDB = 1; return; } +trfrData* Individual::getTrfrData(void) { + return pTrfrData.get(); +} + // Get phenotypic transfer by SMS traits -trfrSMSTraits Individual::getSMSTraits(void) { +trfrSMSTraits Individual::getIndSMSTraits(void) { + trfrSMSTraits s; s.dp = s.gb = s.alphaDB = 1.0; s.betaDB = 1; - if (smsData != 0) { - s.dp = smsData->dp; s.gb = smsData->gb; - s.alphaDB = smsData->alphaDB; s.betaDB = smsData->betaDB; + if (pTrfrData != 0) { + + auto& pSMS = dynamic_cast(*pTrfrData); + + s.dp = pSMS.dp; s.gb = pSMS.gb; + s.alphaDB = pSMS.alphaDB; s.betaDB = pSMS.betaDB; } + return s; } + // Set phenotypic transfer by CRW traits -void Individual::setCRWTraits(Species* pSpecies, short CRWgenelocn, short nCRWtraits, - bool sexdep) { +void Individual::setIndCRWTraits(Species* pSpecies) { trfrCRWTraits c; c.stepLength = c.rho = 0.0; - if (pGenome != 0) { - if (pSpecies->has1ChromPerTrait()) { - if (sexdep) { - c.stepLength = (float)pGenome->express(CRWgenelocn + sex, 0, sex); - c.rho = (float)pGenome->express(CRWgenelocn + 2 + sex, 0, sex); - } - else { - c.stepLength = (float)pGenome->express(CRWgenelocn, 0, 0); - c.rho = (float)pGenome->express(CRWgenelocn + 1, 0, 0); - } - } - else { - if (sexdep) { - c.stepLength = (float)pGenome->express(pSpecies, CRWgenelocn + sex); - c.rho = (float)pGenome->express(pSpecies, CRWgenelocn + 2 + sex); - } - else { - c.stepLength = (float)pGenome->express(pSpecies, CRWgenelocn); - c.rho = (float)pGenome->express(pSpecies, CRWgenelocn + 1); - } - } - } - trfrCRWParams cparams; - if (sexdep) { - cparams = pSpecies->getCRWParams(0, sex); - } - else { - cparams = pSpecies->getCRWParams(0, 0); - } - crw->stepL = (float)(c.stepLength * cparams.stepLScale + cparams.stepLgthMean); - crw->rho = (float)(c.rho * cparams.rhoScale + cparams.rhoMean); - if (crw->stepL < 1.0) crw->stepL = 1.0; - if (crw->rho < 0.0) crw->rho = 0.0; - if (crw->rho > 0.999) crw->rho = 0.999f; + c.stepLength = getTrait(CRW_STEPLENGTH)->express(); + c.rho = getTrait(CRW_STEPCORRELATION)->express(); + + auto& pCRW = dynamic_cast(*pTrfrData); + pCRW.stepLength = (float)(c.stepLength); + pCRW.rho = (float)(c.rho); + if (pCRW.stepLength < 1.0) pCRW.stepLength = 1.0; + if (pCRW.rho < 0.0) pCRW.rho = 0.0; + if (pCRW.rho > 0.999) pCRW.rho = 0.999f; return; } // Get phenotypic transfer by CRW traits -trfrCRWTraits Individual::getCRWTraits(void) { - trfrCRWTraits c; c.stepLength = c.rho = 0.0; - if (crw != 0) { - c.stepLength = crw->stepL; - c.rho = crw->rho; +trfrCRWTraits Individual::getIndCRWTraits(void) { + + trfrCRWTraits c; + c.stepLength = c.rho = 0.0; + if (pTrfrData != 0) { + auto& pCRW = dynamic_cast(*pTrfrData); + c.stepLength = pCRW.stepLength; + c.rho = pCRW.rho; } return c; -} -// Set phenotypic settlement traits -void Individual::setSettTraits(Species* pSpecies, short settgenelocn, short nsetttraits, - bool sexdep) { - settleTraits s; s.s0 = s.alpha = s.beta = 0.0; - if (pGenome != 0) { - if (pSpecies->has1ChromPerTrait()) { - if (sexdep) { - s.s0 = (float)pGenome->express(settgenelocn + 3 * sex, 0, 0); - s.alpha = (float)pGenome->express(settgenelocn + 3 * sex + 1, 0, 0); - s.beta = (float)pGenome->express(settgenelocn + 3 * sex + 2, 0, 0); } - else { - s.s0 = (float)pGenome->express(settgenelocn, 0, 0); - s.alpha = (float)pGenome->express(settgenelocn + 1, 0, 0); - s.beta = (float)pGenome->express(settgenelocn + 2, 0, 0); - } - } - else { - if (sexdep) { - s.s0 = (float)pGenome->express(pSpecies, settgenelocn + 3 * sex); - s.alpha = (float)pGenome->express(pSpecies, settgenelocn + 3 * sex + 1); - s.beta = (float)pGenome->express(pSpecies, settgenelocn + 3 * sex + 2); - } - else { - s.s0 = (float)pGenome->express(pSpecies, settgenelocn); - s.alpha = (float)pGenome->express(pSpecies, settgenelocn + 1); - s.beta = (float)pGenome->express(pSpecies, settgenelocn + 2); - } - - } - } - - settParams sparams; - if (sexdep) { - sparams = pSpecies->getSettParams(0, sex); - } - else { - sparams = pSpecies->getSettParams(0, 0); - } - setttraits = new settleTraits; - setttraits->s0 = (float)(s.s0 * sparams.s0Scale + sparams.s0Mean); - setttraits->alpha = (float)(s.alpha * sparams.alphaSScale + sparams.alphaSMean); - setttraits->beta = (float)(s.beta * sparams.betaSScale + sparams.betaSMean); - if (setttraits->s0 < 0.0) setttraits->s0 = 0.0; - if (setttraits->s0 > 1.0) setttraits->s0 = 1.0; - return; -} // Get phenotypic settlement traits -settleTraits Individual::getSettTraits(void) { +settleTraits Individual::getIndSettTraits(void) { settleTraits s; s.s0 = s.alpha = s.beta = 0.0; - if (setttraits != 0) { - s.s0 = setttraits->s0; - s.alpha = setttraits->alpha; - s.beta = setttraits->beta; + if (pSettleTraits != 0) { + s.s0 = pSettleTraits->s0; + s.alpha = pSettleTraits->alpha; + s.beta = pSettleTraits->beta; } return s; @@ -818,11 +682,11 @@ settleTraits Individual::getSettTraits(void) { void Individual::setStatus(short s) { - if (s >= 0 && s <= 9) status = s; + if (s >= 0 && s <= 10) status = s; status = s; } -void Individual::developing(void) { +void Individual::setToDevelop(void) { isDeveloping = true; } @@ -831,7 +695,7 @@ void Individual::develop(void) { } void Individual::ageIncrement(short maxage) { - if (status < 6) { // alive + if (status < 6 || status == 10) { // alive age++; if (age > maxage) status = 9; // exceeds max. age - dies else { @@ -855,8 +719,7 @@ void Individual::moveto(Cell* newCell) { double d = sqrt(((double)currloc.x - (double)newloc.x) * ((double)currloc.x - (double)newloc.x) + ((double)currloc.y - (double)newloc.y) * ((double)currloc.y - (double)newloc.y)); if (d >= 1.0 && d < 1.5) { // ok - pCurrCell = newCell; - status = 5; + pCurrCell = newCell; status = 5; } } @@ -864,17 +727,14 @@ void Individual::moveto(Cell* newCell) { // Move to a new cell by sampling a dispersal distance from a single or double // negative exponential kernel // Returns 1 if still dispersing (including having found a potential patch), otherwise 0 -int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, - const short repType, const bool absorbing) +int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, const bool absorbing) { - - intptr patch; int patchNum = 0; int newX = 0, newY = 0; int dispersing = 1; double xrand, yrand, meandist, dist, r1, rndangle, nx, ny; float localK; - trfrKernTraits kern; + trfrKernelParams kern; Cell* pCell; Patch* pPatch; locn loc = pCurrCell->getLocn(); @@ -882,7 +742,7 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, landData land = pLandscape->getLandData(); bool usefullkernel = pSpecies->useFullKernel(); - trfrRules trfr = pSpecies->getTrfr(); + transferRules trfr = pSpecies->getTransferRules(); settleRules sett = pSpecies->getSettRules(stage, sex); pCell = NULL; @@ -890,30 +750,31 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, if (trfr.indVar) { // get individual's kernel parameters kern.meanDist1 = kern.meanDist2 = kern.probKern1 = 0.0; - if (pGenome != 0) { - kern.meanDist1 = kerntraits->meanDist1; + + auto& pKernel = dynamic_cast(*pTrfrData); + + kern.meanDist1 = pKernel.meanDist1; if (trfr.twinKern) { - kern.meanDist2 = kerntraits->meanDist2; - kern.probKern1 = kerntraits->probKern1; + kern.meanDist2 = pKernel.meanDist2; + kern.probKern1 = pKernel.probKern1; } } - } else { // get kernel parameters for the species if (trfr.sexDep) { if (trfr.stgDep) { - kern = pSpecies->getKernTraits(stage, sex); + kern = pSpecies->getSpKernTraits(stage, sex); } else { - kern = pSpecies->getKernTraits(0, sex); + kern = pSpecies->getSpKernTraits(0, sex); } } else { if (trfr.stgDep) { - kern = pSpecies->getKernTraits(stage, 0); + kern = pSpecies->getSpKernTraits(stage, 0); } else { - kern = pSpecies->getKernTraits(0, 0); + kern = pSpecies->getSpKernTraits(0, 0); } } } @@ -928,10 +789,12 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, } else meandist = kern.meanDist1 / (float)land.resol; - + // scaled mean may not be less than 1 unless emigration derives from the kernel // (i.e. the 'use full kernel' option is applied) +# ifdef NDEBUG // bypass this requirement for tests if (!usefullkernel && meandist < 1.0) meandist = 1.0; +# endif int loopsteps = 0; // new counter to prevent infinite loop added 14/8/15 do { @@ -950,55 +813,57 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, } } // randomise the position of the individual inside the cell + // so x and y are a corner of the cell? xrand = (double)loc.x + pRandom->Random() * 0.999; yrand = (double)loc.y + pRandom->Random() * 0.999; + // draw factor r1 0 < r1 <= 1 r1 = 0.0000001 + pRandom->Random() * (1.0 - 0.0000001); - // dist = (-1.0*meandist)*std::log(r1); - dist = (-1.0 * meandist) * log(r1); // for LINUX_CLUSTER + dist = (-1.0 * meandist) * log(r1); rndangle = pRandom->Random() * 2.0 * PI; nx = xrand + dist * sin(rndangle); ny = yrand + dist * cos(rndangle); if (nx < 0.0) newX = -1; else newX = (int)nx; if (ny < 0.0) newY = -1; else newY = (int)ny; -#if RSDEBUG +#ifndef NDEBUG if (path != 0) (path->year)++; #endif loopsteps++; } while (loopsteps < 1000 && - ((!absorbing && (newX < land.minX || newX > land.maxX - || newY < land.minY || newY > land.maxY)) + // keep drawing if out of bounds of landscape or same cell + ((!absorbing + && (newX < land.minX || newX > land.maxX || newY < land.minY || newY > land.maxY)) || (!usefullkernel && newX == loc.x && newY == loc.y)) ); + if (loopsteps < 1000) { if (newX < land.minX || newX > land.maxX || newY < land.minY || newY > land.maxY) { // beyond absorbing boundary + // this cannot be reached if not absorbing? pCell = 0; - patch = 0; + pPatch = nullptr; patchNum = -1; } else { pCell = pLandscape->findCell(newX, newY); if (pCell == 0) { // no-data cell - patch = 0; + pPatch = nullptr; patchNum = -1; } else { - patch = pCell->getPatch(); - if (patch == 0) { // matrix - pPatch = 0; + pPatch = pCell->getPatch(); + if (pPatch == nullptr) { // matrix patchNum = 0; } else { - pPatch = (Patch*)patch; patchNum = pPatch->getPatchNum(); } } } } - else { - patch = 0; + else { // exceeded 1000 attempts + pPatch = nullptr; patchNum = -1; } } while (!absorbing && patchNum < 0 && loopsteps < 1000); // in a no-data region @@ -1006,6 +871,7 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, if (loopsteps < 1000) { if (pCell == 0) { // beyond absorbing boundary or in no-data cell + // only if absorbing=true and out of bounddaries pCurrCell = 0; status = 6; dispersing = 0; @@ -1018,6 +884,7 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, status = 2; // record as potential settler } else { + // unsuitable patch dispersing = 0; // can wait in matrix if population is stage structured ... if (pSpecies->stageStructured()) { @@ -1028,19 +895,18 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, else // ... it is not status = 6; // dies (unless there is a suitable neighbouring cell) } - else - status = 6; // dies (unless there is a suitable neighbouring cell) + else status = 6; // dies (unless there is a suitable neighbouring cell) } } } - else { + else { // exceeded 1000 attempts status = 6; dispersing = 0; } // apply dispersal-related mortality, which may be distance-dependent dist *= (float)land.resol; // re-scale distance moved to landscape scale - if (status < 7) { + if (status < 7 || status == 10) { double dispmort; trfrMortParams mort = pSpecies->getMortParams(); if (trfr.distMort) { @@ -1064,10 +930,8 @@ int Individual::moveKernel(Landscape* pLandscape, Species* pSpecies, int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, const short landIx, const bool absorbing) { - if (status != 1) return 0; // not currently dispersing - intptr patch; int patchNum; int newX, newY; locn loc; @@ -1076,24 +940,23 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, double angle; double mortprob, rho, steplen; movedata move; - Patch* pPatch = 0; + Patch* pPatch = nullptr; bool absorbed = false; + //int popsize; landData land = pLandscape->getLandData(); simParams sim = paramsSim->getSim(); - trfrRules trfr = pSpecies->getTrfr(); - trfrCRWTraits movt = pSpecies->getCRWTraits(); + transferRules trfr = pSpecies->getTransferRules(); + trfrCRWTraits movt = pSpecies->getSpCRWTraits(); settleSteps settsteps = pSpecies->getSteps(stage, sex); - patch = pCurrCell->getPatch(); + pPatch = pCurrCell->getPatch(); - if (patch == 0) { // matrix - pPatch = 0; + if (pPatch == nullptr) { // matrix patchNum = 0; } else { - pPatch = (Patch*)patch; patchNum = pPatch->getPatchNum(); } // apply step-dependent mortality risk ... @@ -1116,15 +979,14 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, } else { // take a step (path->year)++; - (path->total)++; - if (patch == 0 || pPatch == 0 || patchNum == 0) { // not in a patch + (path->total)++; + if (pPatch == nullptr || patchNum == 0) { // not in a patch if (path != 0) path->settleStatus = 0; // reset path settlement status (path->out)++; } loc = pCurrCell->getLocn(); newX = loc.x; newY = loc.y; - switch (trfr.moveType) { case 1: // SMS @@ -1137,16 +999,8 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, dispersing = 0; } else { - // WOULD IT BE MORE EFFICIENT FOR smsMove TO RETURN A POINTER TO THE NEW CELL? ... - - patch = pCurrCell->getPatch(); - if (patch == 0) { - pPatch = 0; - } - else { - pPatch = (Patch*)patch; - } + pPatch = pCurrCell->getPatch(); if (sim.saveVisits && pPatch != pNatalPatch) { pCurrCell->incrVisits(); } @@ -1154,20 +1008,21 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, break; case 2: // CRW + + auto & pCRW = dynamic_cast(*pTrfrData); + if (trfr.indVar) { - if (crw != 0) { - movt.stepLength = crw->stepL; - movt.rho = crw->rho; + movt.stepLength = pCRW.stepLength; + movt.rho = pCRW.rho; } - } - steplen = movt.stepLength; if (steplen < 0.2 * land.resol) steplen = 0.2 * land.resol; - rho = movt.rho; if (rho > 0.99) rho = 0.99; + steplen = movt.stepLength; + rho = movt.rho; if (pPatch == pNatalPatch) { rho = 0.99; // to promote leaving natal patch path->out = 0; } - if (movt.straigtenPath && path->settleStatus > 0) { + if (movt.straightenPath && path->settleStatus > 0) { // individual is in a patch and has already determined whether to settle rho = 0.99; // to promote leaving the patch path->out = 0; @@ -1180,13 +1035,13 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, || pCurrCell == 0) { // individual has tried to go out-of-bounds or into no-data area // allow random move to prevent repeated similar move - angle = wrpcauchy(crw->prevdrn, 0.0); + angle = wrpcauchy(pCRW.prevdrn, 0.0); } else - angle = wrpcauchy(crw->prevdrn, rho); + angle = wrpcauchy(pCRW.prevdrn, rho); // new continuous cell coordinates - xcnew = crw->xc + sin(angle) * steplen / (float)land.resol; - ycnew = crw->yc + cos(angle) * steplen / (float)land.resol; + xcnew = pCRW.xc + sin(angle) * steplen / (float)land.resol; + ycnew = pCRW.yc + cos(angle) * steplen / (float)land.resol; if (xcnew < 0.0) newX = -1; else newX = (int)xcnew; if (ycnew < 0.0) newY = -1; else newY = (int)ycnew; loopsteps++; @@ -1197,14 +1052,14 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, else pCurrCell = pLandscape->findCell(newX, newY); if (pCurrCell == 0) { // no-data cell or beyond absorbing boundary - patch = 0; + pPatch = nullptr; if (absorbing) absorbed = true; } else - patch = pCurrCell->getPatch(); + pPatch = pCurrCell->getPatch(); } while (!absorbing && pCurrCell == 0 && loopsteps < 1000); - crw->prevdrn = (float)angle; - crw->xc = (float)xcnew; crw->yc = (float)ycnew; + pCRW.prevdrn = (float)angle; + pCRW.xc = (float)xcnew; pCRW.yc = (float)ycnew; if (absorbed) { // beyond absorbing boundary or in no-data square status = 6; dispersing = 0; @@ -1224,11 +1079,9 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, break; } // end of switch (trfr.moveType) - if (dispersing == 1 && - patch > 0 // not no-data area or matrix + pPatch != nullptr // not no-data area or matrix && path->total >= settsteps.minSteps) { - pPatch = (Patch*)patch; if (pPatch != pNatalPatch) { // determine whether the new patch is potentially suitable @@ -1250,7 +1103,6 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, } // end of single movement step return dispersing; - } //--------------------------------------------------------------------------- @@ -1261,12 +1113,10 @@ int Individual::moveStep(Landscape* pLandscape, Species* pSpecies, movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, const short landIx, const bool natalPatch, const bool indvar, const bool absorbing) { - array3x3d nbr; // to hold weights/costs/probs of moving to neighbouring cells array3x3d goal; // to hold weights for moving towards a goal location array3x3f hab; // to hold weights for habitat (includes percep range) - int x2, y2; // x index from 0=W to 2=E, y index from 0=N to 2=S - int newX = -9, newY = -9; + int newX = -9, newY = -9; // BUGFIX: must not be 0 because 0,0 is a valid landscape cell Cell* pCell; Cell* pNewCell = NULL; double sum_nbrs = 0.0; @@ -1274,6 +1124,7 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, int cellcost, newcellcost; locn current; + auto& pSMS = dynamic_cast(*pTrfrData); if (pCurrCell == 0) { // x,y is a NODATA square - this should not occur here @@ -1283,19 +1134,19 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, } landData land = pLand->getLandData(); - trfrSMSTraits movt = pSpecies->getSMSTraits(); + trfrSMSTraits movt = pSpecies->getSpSMSTraits(); current = pCurrCell->getLocn(); //get weights for directional persistence.... if ((path->out > 0 && path->out <= (movt.pr + 1)) || natalPatch - || (movt.straigtenPath && path->settleStatus > 0)) { + || (movt.straightenPath && path->settleStatus > 0)) { // inflate directional persistence to promote leaving the patch - if (indvar) nbr = getSimDir(current.x, current.y, 10.0f * smsData->dp); + if (indvar) nbr = getSimDir(current.x, current.y, 10.0f * pSMS.dp); else nbr = getSimDir(current.x, current.y, 10.0f * movt.dp); } else { - if (indvar) nbr = getSimDir(current.x, current.y, smsData->dp); + if (indvar) nbr = getSimDir(current.x, current.y, pSMS.dp); else nbr = getSimDir(current.x, current.y, movt.dp); } if (natalPatch || path->settleStatus > 0) path->out = 0; @@ -1311,12 +1162,13 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, nsteps = path->total; } if (indvar) { - double exp_arg = -((double)nsteps - (double)smsData->betaDB) * (-smsData->alphaDB); + double exp_arg = -((double)nsteps - (double)pSMS.betaDB) * (-pSMS.alphaDB); if (exp_arg > 100.0) exp_arg = 100.0; // to prevent exp() overflow error - gb = 1.0 + (smsData->gb - 1.0) / (1.0 + exp(exp_arg)); + gb = 1.0 + (pSMS.gb - 1.0) / (1.0 + exp(exp_arg)); } else { double exp_arg = -((double)nsteps - (double)movt.betaDB) * (-movt.alphaDB); + if (exp_arg > 100.0) exp_arg = 100.0; // to prevent exp() overflow error gb = 1.0 + (movt.gb - 1.0) / (1.0 + exp(exp_arg)); } @@ -1326,22 +1178,25 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, // get habitat-dependent weights (mean effective costs, given perceptual range) // first check if costs have already been calculated + { +#ifdef _OPENMP + const std::unique_lock lock = pCurrCell->lockCost(); +#endif + hab = pCurrCell->getEffCosts(); + if (hab.cell[0][0] < 0.0) { // costs have not already been calculated + hab = getHabMatrix(pLand, pSpecies, current.x, current.y, movt.pr, movt.prMethod, + landIx, absorbing); + pCurrCell->setEffCosts(hab); + } + else { // they have already been calculated - no action required - hab = pCurrCell->getEffCosts(); - - if (hab.cell[0][0] < 0.0) { // costs have not already been calculated - hab = getHabMatrix(pLand, pSpecies, current.x, current.y, movt.pr, movt.prMethod, - landIx, absorbing); - pCurrCell->setEffCosts(hab); - } - else { - // they have already been calculated - no action required + } } // determine weighted effective cost for the 8 neighbours // multiply directional persistence, goal bias and habitat habitat-dependent weights - for (y2 = 2; y2 > -1; y2--) { - for (x2 = 0; x2 < 3; x2++) { + for (int y2 = 2; y2 > -1; y2--) { + for (int x2 = 0; x2 < 3; x2++) { if (x2 == 1 && y2 == 1) nbr.cell[x2][y2] = 0.0; else { if (x2 == 1 || y2 == 1) //not diagonal @@ -1353,8 +1208,8 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, } // determine reciprocal of effective cost for the 8 neighbours - for (y2 = 2; y2 > -1; y2--) { - for (x2 = 0; x2 < 3; x2++) { + for (int y2 = 2; y2 > -1; y2--) { + for (int x2 = 0; x2 < 3; x2++) { if (nbr.cell[x2][y2] > 0.0) nbr.cell[x2][y2] = 1.0f / nbr.cell[x2][y2]; } } @@ -1363,8 +1218,8 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, // to have zero probability // increment total for re-scaling to sum to unity - for (y2 = 2; y2 > -1; y2--) { - for (x2 = 0; x2 < 3; x2++) { + for (int y2 = 2; y2 > -1; y2--) { + for (int x2 = 0; x2 < 3; x2++) { if (!absorbing) { if ((current.y + y2 - 1) < land.minY || (current.y + y2 - 1) > land.maxY || (current.x + x2 - 1) < land.minX || (current.x + x2 - 1) > land.maxX) @@ -1375,15 +1230,14 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, if (pCell == 0) nbr.cell[x2][y2] = 0.0; // no-data cell } } - sum_nbrs += nbr.cell[x2][y2]; } } // scale effective costs as probabilities summing to 1 if (sum_nbrs > 0.0) { // should always be the case, but safest to check... - for (y2 = 2; y2 > -1; y2--) { - for (x2 = 0; x2 < 3; x2++) { + for (int y2 = 2; y2 > -1; y2--) { + for (int x2 = 0; x2 < 3; x2++) { nbr.cell[x2][y2] = nbr.cell[x2][y2] / (float)sum_nbrs; } } @@ -1393,13 +1247,15 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, double cumulative[9]; int j = 0; cumulative[0] = nbr.cell[0][0]; - for (y2 = 0; y2 < 3; y2++) { - for (x2 = 0; x2 < 3; x2++) { + for (int y2 = 0; y2 < 3; y2++) { + for (int x2 = 0; x2 < 3; x2++) { if (j != 0) cumulative[j] = cumulative[j - 1] + nbr.cell[x2][y2]; j++; } } + //to prevent very rare bug that random draw is greater than 0.999999999 + if (cumulative[8] != 1) cumulative[8] = 1; // select direction at random based on cell selection probabilities // landscape boundaries and no-data cells may be reflective or absorbing cellcost = pCurrCell->getCost(); @@ -1408,8 +1264,8 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, do { double rnd = pRandom->Random(); j = 0; - for (y2 = 0; y2 < 3; y2++) { - for (x2 = 0; x2 < 3; x2++) { + for (int y2 = 0; y2 < 3; y2++) { + for (int x2 = 0; x2 < 3; x2++) { if (rnd < cumulative[j]) { newX = current.x + x2 - 1; newY = current.y + y2 - 1; @@ -1444,10 +1300,11 @@ movedata Individual::smsMove(Landscape* pLand, Species* pSpecies, newcellcost = pNewCell->getCost(); move.cost = move.dist * 0.5f * ((float)cellcost + (float)newcellcost); // make the selected move - if ((short)memory.size() == movt.memSize) { + if (memory.full()) { memory.pop(); // remove oldest memory element } memory.push(current); // record previous location in memory + //if (write_out) out << "queue length is " << memory.size() << endl; pCurrCell = pNewCell; } return move; @@ -1462,6 +1319,7 @@ array3x3d Individual::getSimDir(const int x, const int y, const float dp) double theta; int xx, yy; + //if (write_out) out<<"step 0"<(*pTrfrData); if (goaltype == 0) { // no goal set for (xx = 0; xx < 3; xx++) { @@ -1512,7 +1378,7 @@ array3x3d Individual::getGoalBias(const int x, const int y, } else { d.cell[1][1] = 0; - if ((x - smsData->goal.x) == 0 && (y - smsData->goal.y) == 0) { + if ((x - pSMS.goal.x) == 0 && (y - pSMS.goal.y) == 0) { // at goal, set matrix to unity for (xx = 0; xx < 3; xx++) { for (yy = 0; yy < 3; yy++) { @@ -1532,7 +1398,8 @@ array3x3d Individual::getGoalBias(const int x, const int y, return d; } else // goaltype == 2 - theta = atan2(((double)x - (double)smsData->goal.x), ((double)y - (double)smsData->goal.y)); + theta = atan2(((double)x - (double)pSMS.goal.x), ((double)y - (double)pSMS.goal.y)); + // if (write_out) out<<"goalx,goaly: "<getLandData(); - if (absorbing) nodatacost = ABSNODATACOST; - else nodatacost = NODATACOST; + if (absorbing) nodatacost = gAbsorbingNoDataCost; + else nodatacost = gNoDataCost; for (int x2 = -1; x2 < 2; x2++) { // index of relative move in x direction for (int y2 = -1; y2 < 2; y2++) { // index of relative move in x direction @@ -1635,6 +1502,7 @@ array3x3f Individual::getHabMatrix(Landscape* pLand, Species* pSpecies, // calculate effective mean cost of cells in perceptual range ncells = 0; weight = 0.0; sumweights = 0.0; + // targetseen = 0; if (x2 != 0 || y2 != 0) { // not central cell (i.e. current cell) for (int x3 = xmin; x3 <= xmax; x3++) { for (int y3 = ymin; y3 <= ymax; y3++) { @@ -1644,6 +1512,10 @@ array3x3f Individual::getHabMatrix(Landscape* pLand, Species* pSpecies, else { if ((x + x3) > land.maxX) x4 = x + x3 - land.maxX - 1; else x4 = x + x3; } if ((y + y3) < 0) y4 = y + y3 + land.maxY + 1; else { if ((y + y3) > land.maxY) y4 = y + y3 - land.maxY - 1; else y4 = y + y3; } + // if (write_out && (x4 < 0 || y4 < 0)) { + // out<<"ERROR: x "< land.maxX || y4 < 0 || y4 > land.maxY) { // unexpected problem - e.g. due to ridiculously large PR // treat as a no-data cell @@ -1665,7 +1537,7 @@ array3x3f Individual::getHabMatrix(Landscape* pLand, Species* pSpecies, pCell->setCost(cost); } else { - + // nothing? } } } @@ -1711,6 +1583,8 @@ array3x3f Individual::getHabMatrix(Landscape* pLand, Species* pSpecies, } } } + // if (write_out2) out2<<"effective mean cost "<outGenetics(rep, year, spnum, indId, xtab); - } - } - else { // open/close file - pGenome->outGenHeaders(rep, landNr, xtab); - } - -} - #if RS_RCPP +#ifdef _OPENMP +std::mutex outMovePaths_mutex; +#endif + //--------------------------------------------------------------------------- // Write records to movement paths file void Individual::outMovePath(const int year) @@ -1743,6 +1605,9 @@ void Individual::outMovePath(const int year) //if (pPatch != pNatalPatch) { loc = pCurrCell->getLocn(); +#ifdef _OPENMP + const std::lock_guard lock(outMovePaths_mutex); +#endif // if still dispersing... if (status == 1) { // at first step, record start cell first @@ -1760,7 +1625,7 @@ void Individual::outMovePath(const int year) << endl; } // if not anymore dispersing... - if (status > 1 && status < 10) { + if (status > 1 && status <= 10) { prev_loc = pPrevCell->getLocn(); // record only if this is the first step as non-disperser if (path->pathoutput) { @@ -1812,51 +1677,72 @@ double cauchy(double location, double scale) { //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- +#ifdef UNIT_TESTS -#if RSDEBUG - - -void testIndividual() { - - Patch* pPatch = new Patch(0, 0); - int cell_x = 2; - int cell_y = 5; - int cell_hab = 2; - Cell* pCell = new Cell(cell_x, cell_y, (intptr)pPatch, cell_hab); - - // Create an individual - short stg = 0; - short age = 0; - short repInt = 0; - float probmale = 0; - bool uses_movt_process = true; - short moveType = 1; - Individual ind(pCell, pPatch, stg, age, repInt, probmale, uses_movt_process, moveType); - - // An individual can move to a neighbouring cell - //ind.moveto(); - - // Gets its sex drawn from pmale - - // Can age or develop - - // - - // Reproduces - // depending on whether it is sexual or not - // depending on the stage - // depending on the trait inheritance +Cell* Individual::getCurrCell() const { + return pCurrCell; +} +void Individual::setInitAngle(const float angle) { + auto pCRW = dynamic_cast(pTrfrData.get()); + pCRW->prevdrn = angle; +} - // Disperses - // Emigrates - // Transfers - // Settles +// Force mutations to trigger for all traits +void Individual::triggerMutations(Species* pSp) { + for (auto const& [trType, indTrait] : spTraitTable) { + indTrait->mutate(); + if (trType == GENETIC_LOAD1 + || trType == GENETIC_LOAD2 + || trType == GENETIC_LOAD3 + || trType == GENETIC_LOAD4 + || trType == GENETIC_LOAD5) + geneticFitness *= indTrait->express(); + } + this->expressDispersalPhenotypes(pSp, 1.0); +} - // Survives +// Shorthand function to edit a genotype with custom values +void Individual::overrideGenotype(TraitType whichTrait, const map>>& newGenotype) { - // Develops + GeneticFitnessTrait* pGenFitTrait; + DispersalTrait* pDispTrait; -} -#endif // RSDEBUG + switch (whichTrait) + { + case GENETIC_LOAD1: case GENETIC_LOAD2: case GENETIC_LOAD3: case GENETIC_LOAD4: case GENETIC_LOAD5: + pGenFitTrait = dynamic_cast(this->getTrait(whichTrait)); + pGenFitTrait->getGenes() = newGenotype; + break; + case E_D0: case E_ALPHA: case E_BETA: + case S_S0: case S_ALPHA: case S_BETA: + case E_D0_F: case E_ALPHA_F: case E_BETA_F: + case S_S0_F: case S_ALPHA_F: case S_BETA_F: + case E_D0_M: case E_ALPHA_M: case E_BETA_M: + case S_S0_M: case S_ALPHA_M: case S_BETA_M: + case CRW_STEPLENGTH: case CRW_STEPCORRELATION: + case KERNEL_MEANDIST_1: case KERNEL_MEANDIST_2: case KERNEL_PROBABILITY: + case KERNEL_MEANDIST_1_F: case KERNEL_MEANDIST_2_F: case KERNEL_PROBABILITY_F: + case KERNEL_MEANDIST_1_M: case KERNEL_MEANDIST_2_M: case KERNEL_PROBABILITY_M: + case SMS_DP: case SMS_GB: case SMS_ALPHADB: case SMS_BETADB: + pDispTrait = dynamic_cast(this->getTrait(whichTrait)); + pDispTrait->getGenes() = newGenotype; + break; + default: + throw logic_error("Wrong trait type: please choose a valid dispersal or genetic fitness trait."); + break; + } +}; + +void Individual::overrideGenotype(TraitType whichTrait, const map>& newGenotype) { + + if (!whichTrait == NEUTRAL) { + throw logic_error("Attempt to override non-neutral trait with neutral trait genotype.\n"); + } + NeutralTrait* pNeutralTrait; + pNeutralTrait = dynamic_cast(this->getTrait(NEUTRAL)); + pNeutralTrait->getGenes() = newGenotype; +}; + +#endif // UNIT_TESTS diff --git a/Individual.h b/Individual.h index bdc4ecb..7ec8e50 100644 --- a/Individual.h +++ b/Individual.h @@ -1,45 +1,45 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Tho Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * --------------------------------------------------------------------------*/ - - -/*------------------------------------------------------------------------------ -RangeShifter v2.0 Individual -Implements the Individual class -Various optional attributes (genes for traits, movement parameters, etc.) are -allocated dynamically and accessed by pointers if required. + /*------------------------------------------------------------------------------ -For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. -and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. -Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 + RangeShifter v2.0 Individual -Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + Implements the Individual class -Last updated: 26 October 2021 by Steve Palmer + Various optional attributes (genes for traits, movement parameters, etc.) are + allocated dynamically and accessed by pointers if required. -------------------------------------------------------------------------------*/ + For full details of RangeShifter, please see: + Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. + and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial + eco-evolutionary dynamics and species responses to environmental changes. + Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 + + Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + + Last updated: 28 July 2021 by Greta Bocedi + + ------------------------------------------------------------------------------*/ #ifndef IndividualH #define IndividualH @@ -47,6 +47,8 @@ Last updated: 26 October 2021 by Steve Palmer #include #include +#include +#include using namespace std; #include "Parameters.h" @@ -54,24 +56,26 @@ using namespace std; #include "Landscape.h" #include "Patch.h" #include "Cell.h" -#include "Genome.h" +#include "TraitFactory.h" -#define NODATACOST 100000 // cost to use in place of nodata value for SMS -#define ABSNODATACOST 100 // cost to use in place of nodata value for SMS - // when boundaries are absorbing +#ifdef _OPENMP +#include +#endif // _OPENMP //--------------------------------------------------------------------------- struct indStats { - short stage; short sex; short age; short status; short fallow; + short stage; sex_t sex; short age; short status; short fallow; bool isDeveloping; }; struct pathData { // to hold path data common to SMS and CRW models int year, total, out; // nos. of steps Patch* pSettPatch; // pointer to most recent patch tested for settlement short settleStatus; // whether ind may settle in current patch - // 0 = not set, 1 = debarred through density dependence rule - // 2 = OK to settle subject to finding a mate + // 0 = not set, + // 1 = debarred through density dependence rule + // 2 = OK to settle subject to finding a mate + #if RS_RCPP short pathoutput; #endif @@ -82,28 +86,152 @@ struct pathSteps { // nos. of steps for movement model struct settlePatch { Patch* pSettPatch; short settleStatus; }; -struct crwParams { // to hold data for CRW movement model + +struct trfrData { + virtual void addMyself(trfrData& toAdd) = 0; + virtual void clone(const trfrData& copyFrom) = 0; + virtual void divideTraitsBy(int) = 0; + virtual movement_t getType() = 0; + virtual ~trfrData() {} +}; + +struct crwData : trfrData { // to hold data for CRW movement model + float prevdrn; // direction of previous step (UNITS) - float xc,yc; // continuous cell co-ordinates - float stepL; // phenotypic step length (m) + float xc, yc; // continuous cell co-ordinates float rho; // phenotypic step correlation coefficient + float stepLength; // phenotypic step length (m) + + crwData(float prevdrnA, float xcA, float ycA) : prevdrn(prevdrnA), xc(xcA), yc(ycA), rho(0.0), stepLength(0.0) {} + ~crwData() {} + + void addMyself(trfrData& toAdd) { + auto& CRW = dynamic_cast(toAdd); + CRW.stepLength += stepLength; + CRW.rho += rho; + } + + movement_t getType() { return CRW; } + + void clone(const trfrData& copyFrom) { + const crwData& pCopy = dynamic_cast(copyFrom); + stepLength = pCopy.stepLength; + rho = pCopy.rho; + } + + void divideTraitsBy(int i) { + stepLength /= i; + rho /= i; + } + }; struct array3x3d { double cell[3][3]; }; struct movedata { float dist; float cost; }; -struct smsdata { +struct smsData : trfrData { locn prev; // location of previous cell locn goal; // location of goal float dp; // directional persistence float gb; // goal bias float alphaDB; // dispersal bias decay rate int betaDB; // dispersal bias decay inflection point (no. of steps) + + smsData(locn prevA, locn goalA) : prev(prevA), goal(goalA), dp(0.0), gb(0.0), alphaDB(0.0), betaDB(0) {} + ~smsData() {} + + void addMyself(trfrData& toAdd) { + auto& SMS = dynamic_cast(toAdd); + SMS.dp += dp; + SMS.gb += gb; + SMS.alphaDB += alphaDB; + SMS.betaDB += betaDB; + } + + movement_t getType() { return SMS; } + + void clone(const trfrData& copyFrom) { + auto& pCopy = dynamic_cast(copyFrom); + dp = pCopy.dp; + gb = pCopy.gb; + alphaDB = pCopy.alphaDB; + betaDB = pCopy.betaDB; + } + + void divideTraitsBy(int i) { + dp /= i; + gb /= i; + alphaDB /= i; + betaDB /= i; + } + +}; + +// A class that mimicks std::queue with a fixed-size circular buffer. +template +class MemoryQueue { + std::unique_ptr data; + const std::size_t space; + std::size_t begin_idx; + std::size_t nb_elts; + +public: + MemoryQueue(std::size_t size); + T& front(); + T const& front() const; + T& back(); + T const& back() const; + void push(const T& value); + void push(T&& value); + void pop(); + std::size_t size() const; + bool empty() const; + bool full() const; +}; + +struct kernelData : trfrData { + float meanDist1; + float meanDist2; + float probKern1; + + kernelData(float meanDist1A, float meanDist2A, float probKern1A) : meanDist1(meanDist1A), meanDist2(meanDist2A), probKern1(probKern1A) {} + ~kernelData() {} + + void addMyself(trfrData& toAdd) { + + auto& Kernel = dynamic_cast(toAdd); + + Kernel.meanDist1 += meanDist1; + Kernel.meanDist2 += meanDist2; + Kernel.probKern1 += probKern1; + } + + movement_t getType() { return KERNEL; } + + void clone(const trfrData& copyFrom) { + const kernelData& pCopy = dynamic_cast(copyFrom); + meanDist1 = pCopy.meanDist1; + meanDist2 = pCopy.meanDist2; + probKern1 = pCopy.probKern1; + } + + void divideTraitsBy(int i) { + meanDist1 /= i; + meanDist2 /= i; + probKern1 /= i; + } }; + class Individual { public: +#ifdef _OPENMP + static std::atomic indCounter; // used to create ID, held by class, not members of class +#else static int indCounter; // used to create ID, held by class, not members of class +#endif + static TraitFactory traitFactory; Individual( // Individual constructor + Species*, // pointer to species Cell*, // pointer to Cell Patch*, // pointer to patch short, // stage @@ -114,73 +242,75 @@ class Individual { short // movement type: 1 = SMS, 2 = CRW ); ~Individual(void); - void setGenes( // Set genes for individual variation from species initialisation parameters + void setUpGenes( // Set genes for individual variation from species initialisation parameters Species*, // pointer to Species int // Landscape resolution ); - void setGenes( // Inherit genome from parents + void inheritTraits( // Inherit genome from parents Species*, // pointer to Species Individual*, // pointer to mother Individual*, // pointer to father (must be 0 for an asexual Species) int // Landscape resolution ); - void setEmigTraits( // Set phenotypic emigration traits - Species*, // pointer to Species - short, // location of emigration genes on genome - short, // number of emigration genes - bool // TRUE if emigration is sex-dependent - ); - emigTraits getEmigTraits(void); // Get phenotypic emigration traits - - void setKernTraits( // Set phenotypic transfer by kernel traits - Species*, // pointer to Species - short, // location of kernel genes on genome - short, // number of kernel genes - int, // Landscape resolution - bool // TRUE if transfer is sex-dependent - ); - trfrKernTraits getKernTraits(void); // Get phenotypic transfer by kernel traits - void setSMSTraits( // Set phenotypic transfer by SMS traits - Species*, // pointer to Species - short, // location of SMS genes on genome - short, // number of SMS genes - bool // TRUE if transfer is sex-dependent - ); - trfrSMSTraits getSMSTraits(void); // Get phenotypic transfer by SMS traits - void setCRWTraits( // Set phenotypic transfer by CRW traits - Species*, // pointer to Species - short, // location of CRW genes on genome - short, // number of CRW genes - bool // TRUE if transfer is sex-dependent - ); - trfrCRWTraits getCRWTraits(void); // Get phenotypic transfer by CRW traits + void inheritTraits(Species* pSpecies, Individual* mother, int resol); //haploid + + void expressDispersalPhenotypes(Species* pSpecies, int resol); + + void expressGeneticLoad(Species* pSpecies); + + QuantitativeTrait* getTrait(TraitType trait) const; + + set getTraitTypes(); + + void inherit(Species* pSpecies, const Individual* mother, const Individual* father); + + void inherit(Species* pSpecies, const Individual* mother); // haploid + + void setEmigTraits(Species* pSpecies, bool sexDep, bool densityDep); + void setTransferTraits(Species* pSpecies, transferRules trfr, int resol); + + emigTraits getIndEmigTraits(void); // Get phenotypic emigration traits + + void setIndKernelTraits(Species* pSpecies, bool sexDep, bool twinKernel, int resol); + + trfrKernelParams getIndKernTraits(void); // Get phenotypic transfer by kernel traits + + void setIndSMSTraits(Species* pSpecies); + + trfrSMSTraits getIndSMSTraits(void); // Get phenotypic transfer by SMS traits + + void setIndCRWTraits(Species* pSpecies); + + trfrCRWTraits getIndCRWTraits(void); // Get phenotypic transfer by CRW traits + + void setSettlementTraits(Species* pSpecies, bool sexDep, bool densDep); + + settleTraits getIndSettTraits(void); // Get phenotypic settlement traits + + trfrData* getTrfrData(void); + void setEmigTraits(const emigTraits& emig); + void setSettleTraits(const settleTraits& settle); - void setSettTraits( // Set phenotypic settlement traits - Species*, // pointer to Species - short, // location of settlement genes on genome - short, // number of settlement genes - bool // TRUE if settlement is sex-dependent - ); - settleTraits getSettTraits(void); // Get phenotypic settlement traits // Identify whether an individual is a potentially breeding female - // if so, return her stage, otherwise return 0 int breedingFem(void); int getId(void); - int getSex(void); + sex_t getSex(void); int getStatus(void); + float getGeneticFitness(void); + bool isViable() const; indStats getStats(void); - Cell* getLocn( // Return location (as pointer to Cell) - const short // option: 0 = get natal locn, 1 = get current locn - ); // + Cell* getPrevCell(); // Return previous location (as pointer to Cell) + Cell* getCurrCell(); // Return current location (as pointer to Cell) Patch* getNatalPatch(void); void setYearSteps(int); pathSteps getSteps(void); settlePatch getSettPatch(void); void setSettPatch(const settlePatch); void setStatus(short); - void developing(void); + void setToDevelop(void); void develop(void); void ageIncrement( // Age by one year short // maximum age - if exceeded, the Individual dies @@ -196,7 +326,6 @@ class Individual { int moveKernel( Landscape*, // pointer to Landscape Species*, // pointer to Species - const short, // reproduction type (see Species) const bool // absorbing boundaries? ); // Make a single movement step according to a mechanistic movement model @@ -240,73 +369,68 @@ class Individual { const short, // landscape change index const bool // absorbing boundaries? ); - void outGenetics( // Write records to genetics file - const int, // replicate - const int, // year - const int, // species number - const int, // landscape number - const bool // output as cross table? - ); #if RS_RCPP void outMovePath( // Write records to movement paths file const int // year ); #endif +#ifdef UNIT_TESTS + // Testing utilities + Cell* getCurrCell() const; + void setInitAngle(const float angle); + void insertIndDispTrait(TraitType trType, DispersalTrait tr) { + spTraitTable.insert(make_pair(trType, make_unique(tr))); + }; + void triggerMutations(Species* pSp); + // Shorthand function to edit a genotype with custom values + void overrideGenotype(TraitType whichTrait, const map>>& newGenotype); // dispersal + gen. fitness + void overrideGenotype(TraitType whichTrait, const map>& newGenotype); // neutral +#endif private: int indId; + float geneticFitness; short stage; - short sex; + sex_t sex; short age; - short status; // 0 = initial status in natal patch / philopatric recruit - // 1 = disperser - // 2 = disperser awaiting settlement in possible suitable patch - // 3 = waiting between dispersal events - // 4 = completed settlement - // 5 = completed settlement in a suitable neighbouring cell - // 6 = died during transfer by failing to find a suitable patch - // (includes exceeding maximum number of steps or crossing - // absorbing boundary) - // 7 = died during transfer by constant, step-dependent, - // habitat-dependent or distance-dependent mortality - // 8 = failed to survive annual (demographic) mortality - // 9 = exceeded maximum age + short status; + // 0 = initial status in natal patch / philopatric recruit + // 1 = disperser + // 2 = disperser awaiting settlement in possible suitable patch + // 3 = waiting between dispersal events + // 4 = completed settlement + // 5 = completed settlement in a suitable neighbouring cell + // 6 = died during transfer by failing to find a suitable patch + // (includes exceeding maximum number of steps or crossing + // absorbing boundary) + // 7 = died during transfer by constant, step-dependent, + // habitat-dependent or distance-dependent mortality + // 8 = failed to survive annual (demographic) mortality + // 9 = exceeded maximum age short fallow; // reproductive seasons since last reproduction bool isDeveloping; - Cell *pPrevCell; // pointer to previous Cell - Cell *pCurrCell; // pointer to current Cell - Patch *pNatalPatch; // pointer to natal Patch - emigTraits *emigtraits; // pointer to emigration traits - trfrKernTraits *kerntraits; // pointers to transfer by kernel traits - pathData *path; // pointer to path data for movement model - crwParams *crw; // pointer to CRW traits and data - smsdata *smsData; // pointer to variables required for SMS - settleTraits *setttraits; // pointer to settlement traits - std::queue memory; // memory of last N squares visited for SMS - - Genome *pGenome; - + Cell* pPrevCell; // pointer to previous Cell + Cell* pCurrCell; // pointer to current Cell + Patch* pNatalPatch; // pointer to natal Patch + pathData* path; // pointer to path data for movement model + std::unique_ptr pEmigTraits; // pointer to emigration traits + std::unique_ptr pSettleTraits; // pointer to settlement traits + std::unique_ptr pTrfrData; //can be sms, kernel, crw + MemoryQueue memory; // memory of last N squares visited for SMS + map> spTraitTable; }; //--------------------------------------------------------------------------- -double cauchy(double location, double scale) ; -double wrpcauchy (double location, double rho = exp(double(-1))); +double cauchy(double location, double scale); +double wrpcauchy(double location, double rho = exp(double(-1))); -extern RSrandom *pRandom; - -#if RSDEBUG -extern ofstream DEBUGLOG; -#endif +extern RSrandom* pRandom; #if RS_RCPP extern ofstream outMovePaths; #endif -#if RSDEBUG -void testIndividual(); -#endif - //--------------------------------------------------------------------------- #endif // IndividualH diff --git a/Landscape.cpp b/Landscape.cpp index f1c810a..9dfa04b 100644 --- a/Landscape.cpp +++ b/Landscape.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -40,7 +40,7 @@ ofstream outMovePaths; InitDist::InitDist(Species* pSp) { pSpecies = pSp; - resol = 0; + resol = 1; maxX = 0; maxY = 0; minEast = 0.0; @@ -171,15 +171,7 @@ int InitDist::readDistribution(string distfile) { #endif // open distribution file -#if !RS_RCPP || RSWIN64 dfile.open(distfile.c_str()); -#else - dfile.open(distfile, std::ios::binary); - if (spdistraster.utf) { - // apply BOM-sensitive UTF-16 facet - dfile.imbue(std::locale(dfile.getloc(), new std::codecvt_utf16)); - } -#endif if (!dfile.is_open()) return 21; // read landscape data from header records of distribution file @@ -244,7 +236,48 @@ if (!dfile.eof()) EOFerrorR(distfile); return 0; } +// Read species initial distribution file for threadsafe option +#if RS_RCPP +int InitDist::readDistribution(Rcpp::NumericMatrix distfile, landOrigin habfile_origin, int spResol) { + + int d=0; + double dfloat=0; + int ncols,nrows; + + ncols = distfile.ncol(); + nrows = distfile.nrow(); + + minEast = habfile_origin.minEast; + minNorth = habfile_origin.minNorth; + resol = spResol; + maxX = ncols-1; + maxY = nrows-1; + + for (int y = nrows-1; y >= 0; y--) { + for (int x = 0; x < ncols; x++) { + + dfloat = distfile(nrows-1-y,x); + + if ( !R_IsNA(dfloat) ){ // check for NA + d = (int)dfloat; + if ( d == 0 || d == 1) { // only valid values + if (d == 1) { // species present + cells.push_back(new DistCell(x,y)); + } + } + else { // error in file +#if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found invalid value in species distribution raster." << std::endl; +#endif + return 22; + } + } + } + } + return 0; +} +#endif //--------------------------------------------------------------------------- // Landscape functions @@ -252,7 +285,8 @@ if (!dfile.eof()) EOFerrorR(distfile); Landscape::Landscape(void) { patchModel = false; spDist = false; generated = false; fractal = false; continuous = false; dynamic = false; habIndexed = false; - resol = spResol = landNum = 0; + spatialdemog = false; + resol = spResol = 1; landNum = 0; rasterType = 0; nHab = nHabMax = 0; dimX = dimY = 100; @@ -274,9 +308,7 @@ Landscape::~Landscape() { if (cells != 0) { for (int y = dimY - 1; y >= 0; y--) { - for (int x = 0; x < dimX; x++) { - if (cells[y][x] != 0) delete cells[y][x]; } if (cells[y] != 0) { @@ -286,6 +318,7 @@ Landscape::~Landscape() { delete[] cells; cells = 0; } + int npatches = (int)patches.size(); for (int i = 0; i < npatches; i++) if (patches[i] != NULL) delete patches[i]; @@ -301,27 +334,22 @@ Landscape::~Landscape() { if (initcells[i] != NULL) delete initcells[i]; initcells.clear(); - patchnums.clear(); habCodes.clear(); - colours.clear(); landchanges.clear(); patchchanges.clear(); deleteConnectMatrix(); deletePatchChgMatrix(); if (epsGlobal != 0) delete[] epsGlobal; - } // Remove all patches and cells // Used for replicating artificial landscape without deleting the landscape itself void Landscape::resetLand(void) { - resetLandLimits(); int npatches = (int)patches.size(); for (int i = 0; i < npatches; i++) if (patches[i] != NULL) delete patches[i]; patches.clear(); - if (cells != 0) { for (int y = dimY - 1; y >= 0; y--) { for (int x = 0; x < dimX; x++) { @@ -338,9 +366,12 @@ void Landscape::resetLand(void) { void Landscape::setLandParams(landParams ppp, bool batchMode) { - generated = ppp.generated; patchModel = ppp.patchModel; spDist = ppp.spDist; + generated = ppp.generated; + patchModel = ppp.patchModel; + spDist = ppp.spDist; dynamic = ppp.dynamic; landNum = ppp.landNum; + spatialdemog = ppp.spatialdemog; if (ppp.resol > 0) resol = ppp.resol; if (ppp.spResol > 0 && ppp.spResol % ppp.resol == 0) spResol = ppp.spResol; if ((ppp.rasterType >= 0 && ppp.rasterType <= 2) || ppp.rasterType == 9) @@ -376,6 +407,7 @@ landParams Landscape::getLandParams(void) landParams ppp; ppp.generated = generated; ppp.patchModel = patchModel; ppp.spDist = spDist; ppp.dynamic = dynamic; + ppp.spatialdemog = spatialdemog; ppp.landNum = landNum; ppp.resol = resol; ppp.spResol = spResol; ppp.rasterType = rasterType; @@ -500,32 +532,12 @@ int Landscape::getHabCode(int ixhab) { void Landscape::clearHabitats(void) { habCodes.clear(); - colours.clear(); -} - -void Landscape::addColour(rgb c) { - colours.push_back(c); -} - -void Landscape::changeColour(int i, rgb col) { - int ncolours = (int)colours.size(); - if (i >= 0 && i < ncolours) { - if (col.r >= 0 && col.r <= 255 && col.g >= 0 && col.g <= 255 && col.b >= 0 && col.b <= 255) - colours[i] = col; - } -} - -rgb Landscape::getColour(int ix) { - return colours[ix]; -} - -int Landscape::colourCount(void) { - return (int)colours.size(); } //--------------------------------------------------------------------------- void Landscape::setCellArray(void) { if (cells != 0) resetLand(); + //cells = new Cell **[maxY+1]; cells = new Cell * *[dimY]; for (int y = dimY - 1; y >= 0; y--) { cells[y] = new Cell * [dimX]; @@ -535,23 +547,11 @@ void Landscape::setCellArray(void) { } } -void Landscape::addPatchNum(int p) { - int npatches = (int)patchnums.size(); - bool addpatch = true; - for (int i = 0; i < npatches; i++) { - if (p == patchnums[i]) { - addpatch = false; i = npatches + 1; - } - } - if (addpatch) patchnums.push_back(p); -} - - //--------------------------------------------------------------------------- /* Create an artificial landscape (random or fractal), which can be either binary (habitat index 0 is the matrix, 1 is suitable habitat) or continuous (0 is the matrix, >0 is suitable habitat) */ -void Landscape::generatePatches(void) +void Landscape::generatePatches() { int x, y, ncells; double p; @@ -559,7 +559,6 @@ void Landscape::generatePatches(void) Cell* pCell; vector ArtLandscape; - setCellArray(); int patchnum = 0; // initial patch number for cell-based landscape @@ -727,12 +726,10 @@ void Landscape::allocatePatches(Species* pSpecies) case 2: // habitat quality for (int y = dimY - 1; y >= 0; y--) { for (int x = 0; x < dimX; x++) { - if (cells[y][x] != 0) { // not no-data cell pCell = cells[y][x]; habK = 0.0; int nhab = pCell->nHabitats(); - // for (int i = 0; i < nHab; i++) for (int i = 0; i < nhab; i++) { habK += pSpecies->getHabK(0) * pCell->getHabitat(i) / 100.0f; @@ -788,12 +785,20 @@ void Landscape::addNewCellToLand(int x, int y, int hab) { cells[y][x] = new Cell(x, y, 0, hab); } +void Landscape::addCellToLand(Cell* c) { + if (cells == 0) throw runtime_error("Landscape cells member is uninitialised."); + if (c->getHabIndex(0) < 0.0) + throw logic_error("Can't add no-data cell to landscape."); + locn l = c->getLocn(); + cells[l.y][l.x] = c; +} + void Landscape::addNewCellToPatch(Patch* pPatch, int x, int y, float q) { if (q < 0.0) { // no-data cell - no Cell created cells[y][x] = 0; } else { // create the new cell - cells[y][x] = new Cell(x, y, (intptr)pPatch, q); + cells[y][x] = new Cell(x, y, pPatch, q); if (pPatch != 0) { // not the matrix patch // add the cell to the patch pPatch->addCell(cells[y][x], x, y); @@ -805,7 +810,7 @@ void Landscape::addNewCellToPatch(Patch* pPatch, int x, int y, int hab) { if (hab < 0) // no-data cell - no Cell created cells[y][x] = 0; else { // create the new cell - cells[y][x] = new Cell(x, y, (intptr)pPatch, hab); + cells[y][x] = new Cell(x, y, pPatch, hab); if (pPatch != 0) { // not the matrix patch // add the cell to the patch pPatch->addCell(cells[y][x], x, y); @@ -814,14 +819,14 @@ void Landscape::addNewCellToPatch(Patch* pPatch, int x, int y, int hab) { } void Landscape::addCellToPatch(Cell* pCell, Patch* pPatch) { - pCell->setPatch((intptr)pPatch); + pCell->setPatch(pPatch); locn loc = pCell->getLocn(); // add the cell to the patch pPatch->addCell(pCell, loc.x, loc.y); } void Landscape::addCellToPatch(Cell* pCell, Patch* pPatch, float q) { - pCell->setPatch((intptr)pPatch); + pCell->setPatch(pPatch); // update the habitat type of the cell pCell->setHabitat(q); locn loc = pCell->getLocn(); @@ -830,7 +835,7 @@ void Landscape::addCellToPatch(Cell* pCell, Patch* pPatch, float q) { } void Landscape::addCellToPatch(Cell* pCell, Patch* pPatch, int hab) { - pCell->setPatch((intptr)pPatch); + pCell->setPatch(pPatch); // update the habitat type of the cell pCell->setHabIndex(hab); locn loc = pCell->getLocn(); @@ -867,6 +872,46 @@ Patch* Landscape::findPatch(int num) { return 0; } + +set Landscape::getPatchNbs() const { + set patchNbs; + for (auto& p : patches) + patchNbs.emplace(p->getPatchNum()); + return patchNbs; +} + +set Landscape::samplePatches(const string& samplingOption, int nbToSample, Species* pSpecies) { + + vector sampledPatches; + vector eligiblePatches; + + // Get list of viable patches where the species is present + for (auto p : patches) { + if (p->getPatchNum() == 0) continue; // skip patch 0, the matrix + if (samplingOption == "random" // then all patches are eligible + || p->speciesIsPresent(pSpecies)) // otherwise only patches with at least 1 ind + eligiblePatches.push_back(p->getPatchNum()); + } + + if (samplingOption == "all") { + sampledPatches = eligiblePatches; + } + else if (samplingOption == "random_occupied" || samplingOption == "random") { + if (nbToSample > eligiblePatches.size()) + nbToSample = eligiblePatches.size(); + auto rng = pRandom->getRNG(); + sample(eligiblePatches.begin(), eligiblePatches.end(), std::back_inserter(sampledPatches), + nbToSample, rng); + } + else { + throw logic_error("Sampling option should be random, random_occupied or all when sampling patches."); + } + + set patchIds; + copy(sampledPatches.begin(), sampledPatches.end(), inserter(patchIds, patchIds.end())); + return patchIds; +} + void Landscape::resetPatchPopns(void) { int npatches = (int)patches.size(); for (int i = 0; i < npatches; i++) { @@ -891,11 +936,35 @@ void Landscape::updateCarryingCapacity(Species* pSpecies, int yr, short landIx) } +void Landscape::updateDemoScalings(short landIx) { + + patchLimits landlimits; + landlimits.xMin = minX; landlimits.xMax = maxX; + landlimits.yMin = minY; landlimits.yMax = maxY; + + if(spatialdemog && rasterType == 2) {// demographic scaling only implemented for habitat quality maps + int npatches = (int)patches.size(); // new: for (auto& p : patches) + for (int i = 0; i < npatches; i++) { + if (patches[i]->getPatchNum() != 0) { // not matrix patch + // calculate local scaling for each patch from its constituent cells + patches[i]->setPatchDemoScaling(landIx, landlimits); + } + } + } +} + Cell* Landscape::findCell(int x, int y) { if (x >= 0 && x < dimX && y >= 0 && y < dimY) return cells[y][x]; else return 0; } +bool Landscape::checkDataCell(int x, int y) { + Cell* pCell; + pCell = findCell(x, y); + return true; +} + + int Landscape::patchCount(void) { return (int)patches.size(); } @@ -964,10 +1033,8 @@ void Landscape::updateHabitatIndices(void) { if (cells[y][x] != 0) { // not a no-data cell for (int c = 0; c <= changes; c++) { h = cells[y][x]->getHabIndex(c); - if (h >= 0) { h = findHabCode(h); - cells[y][x]->changeHabIndex(c, h); } } @@ -981,6 +1048,7 @@ void Landscape::setEnvGradient(Species* pSpecies, bool initial) { float dist_from_opt, dev; float habK; + //int hab; double envval; // gradient parameters envGradParams grad = paramsGrad->getGradient(); @@ -1004,7 +1072,6 @@ void Landscape::setEnvGradient(Species* pSpecies, bool initial) break; } } - if (habK > 0.0) { // suitable cell if (initial) { // set local environmental deviation cells[y][x]->setEnvDev((float)pRandom->Random() * (2.0f) - 1.0f); @@ -1087,8 +1154,8 @@ void Landscape::addLandChange(landChange c) { int Landscape::numLandChanges(void) { return (int)landchanges.size(); } landChange Landscape::getLandChange(short ix) { - landChange c; c.chgnum = c.chgyear = 0; - c.habfile = c.pchfile = c.costfile = "none"; + landChange c; c.chgNb = c.chgYear = 0; + c.pathHabFile = c.pathPatchFile = c.pathCostFile = "none"; int nchanges = (int)landchanges.size(); if (ix < nchanges) c = landchanges[ix]; return c; @@ -1098,18 +1165,198 @@ void Landscape::deleteLandChanges(void) { while (landchanges.size() > 0) landchanges.pop_back(); landchanges.clear(); } +#if RS_RCPP +int Landscape::readLandChange(int filenum, Rcpp::NumericMatrix habfile, Rcpp::NumericMatrix pchfile, Rcpp::NumericMatrix costfile, Rcpp::NumericVector scalinglayers){ -#if RS_RCPP && !R_CMD -int Landscape::readLandChange(int filenum, bool costs, wifstream& hfile, wifstream& pfile, wifstream& cfile, int habnodata, int pchnodata, int costnodata) -#else -int Landscape::readLandChange(int filenum, bool costs) + if (filenum < 0) return 19; + + int h = 0, p = 0, c = 0, pchseq = 0; + double hfloat = 0,pfloat = 0,cfloat = 0; + bool costs = false; + if(costfile.nrow()>0 && costfile.ncol()>0) costs = true; + + arma::vec cellDemoScalings; // vector to store local demog scalings + Rcpp::IntegerVector DSdim; + int nrDemogScaleLayers = 0; + if(scalinglayers.attr("dim")==R_NilValue) DSdim = Rcpp::IntegerVector::create(1,1,1); + else{ + DSdim = scalinglayers.attr("dim"); + if(DSdim.size()>2) nrDemogScaleLayers = DSdim[2]; //nr of slices on cube + else nrDemogScaleLayers = 1; + } + arma::cube scalingCube(scalinglayers.begin(),DSdim[0],DSdim[1],nrDemogScaleLayers,false); // turn scaling layers into a cube + + simParams sim = paramsSim->getSim(); + + if (patchModel) pchseq = patchCount(); + + switch (rasterType) { + + case 0: // raster with habitat codes - 100% habitat each cell + + for (int y = dimY-1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + + // get numerics from each raster for this cell + hfloat = habfile(dimY-1-y,x); + if (patchModel) pfloat = pchfile(dimY-1-y,x); + if (costs) cfloat = costfile(dimY-1-y,x); + + if (cells[y][x] != 0) { // not a no data cell (in initial landscape) + if ( R_IsNA(hfloat) ){ // invalid no data cell in change map + return 36; + } + else { + h = (int)hfloat; + if (h < 0 || (sim.batchMode && (h < 1 || h > nHabMax))) { // invalid habitat code + return 33; + } + else { + addHabCode(h); + cells[y][x]->setHabIndex(h); + } + } + if (patchModel) { + if ( R_IsNA(pfloat) ){ + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + #endif + return 34; + } + else { + p = (int)pfloat; + if (p < 0 ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; + #endif + return 34; + } + else { + patchChgMatrix[y][x][2] = p; + if (p > 0 && !existsPatch(p)) { + // addPatchNum(p); // was removed - why? + newPatch(pchseq++,p); + } + } + } + } + if (costs) { + if ( R_IsNA(cfloat) ){ // invalid cost + return 38; + } + else{ + c = (int)cfloat; + if (c < 1) { // invalid cost + return 38; + } + else { + costsChgMatrix[y][x][2] = c; + } + } + } + } + } + } + break; + + case 2: // habitat quality + + for (int y = dimY-1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + + // get numerics from each raster for this cell + hfloat = habfile(dimY-1-y,x); + if (patchModel) pfloat = pchfile(dimY-1-y,x); + if (costs) cfloat = costfile(dimY-1-y,x); + + if (cells[y][x] != 0) { // not a no data cell (in initial landscape) + if ( R_IsNA(hfloat) ){ // invalid no data cell in change map + Rcpp::Rcout << "Found NA in valid habitat cell. For landscape nb "<< filenum + 2 << std::endl; + return 36; + } + else { + if (hfloat < 0.0 || hfloat > 100.0) { // invalid quality score + Rcpp::Rcout << "Found invalid habitat quality value " << hfloat << " in valid habitat cell." << std::endl; + return 37; + } + else { + cells[y][x]->setHabitat((float)hfloat); + } + } + if (patchModel) { + if ( R_IsNA(pfloat) ){ + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + #endif + return 34; + } + else { + p = (int)pfloat; + if (p < 0 ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; + #endif + return 34; + } + else { + patchChgMatrix[y][x][2] = p; + if (p > 0 && !existsPatch(p)) { + //addPatchNum(p); // was removed - why? + newPatch(pchseq++,p); + } + } + } + } + if (costs) { + if ( R_IsNA(cfloat) ){ // invalid cost + return 38; + } + else{ + c = (int)cfloat; + if (c < 1) { // invalid cost + return 38; + } + else { + costsChgMatrix[y][x][2] = c; + } + } + } + //SPATIALDEMOG + // read demographic scalings + if(nrDemogScaleLayers>0){ + // get tube at (y/x) + cellDemoScalings = scalingCube(arma::span(dimY-1-y), arma::span(x), arma::span::all); + if(cellDemoScalings.n_elem==(unsigned)nDSlayer){ + // set vector percentage values in cell + cells[y][x]->addchgDemoScaling(arma::conv_to< std::vector >::from(cellDemoScalings)); + } + else{// invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Wrong number of demographic scaling layers in cell " << x << " ," << y << " at dyn land change nr " << filenum << std::endl; + #endif + return 39; + } + }// SPATIALDEMOG End + + } + } + } + break; + + default: + break; + } + + return 0; +} #endif -{ -#if RSDEBUG - DEBUGLOG << "Landscape::readLandChange(): filenum=" << filenum << " costs=" << int(costs) - << endl; +#if RS_RCPP && !R_CMD // normal file input +int Landscape::readLandChange(int filenum, bool costs, wifstream& hfile, wifstream& pfile, wifstream& cfile, int habnodata, int pchnodata, int costnodata, vector scalinglayers) +#else +int Landscape::readLandChange(int filenum, bool costs, vector scalinglayers) #endif +{ #if RS_RCPP wstring header; @@ -1134,17 +1381,17 @@ int Landscape::readLandChange(int filenum, bool costs) #if !RS_RCPP || R_CMD // open habitat file and optionally also patch and costs files - hfile.open(landchanges[filenum].habfile.c_str()); + hfile.open(landchanges[filenum].pathHabFile.c_str()); if (!hfile.is_open()) return 30; if (patchModel) { - pfile.open(landchanges[filenum].pchfile.c_str()); + pfile.open(landchanges[filenum].pathPatchFile.c_str()); if (!pfile.is_open()) { hfile.close(); hfile.clear(); return 31; } } if (costs) { - cfile.open(landchanges[filenum].costfile.c_str()); + cfile.open(landchanges[filenum].pathCostFile.c_str()); if (!cfile.is_open()) { hfile.close(); hfile.clear(); if (pfile.is_open()) { @@ -1278,7 +1525,7 @@ int Landscape::readLandChange(int filenum, bool costs) else { patchChgMatrix[y][x][2] = p; if (p > 0 && !existsPatch(p)) { - addPatchNum(p); + // addPatchNum(p); why is it removed? newPatch(pchseq++, p); } } @@ -1419,7 +1666,7 @@ int Landscape::readLandChange(int filenum, bool costs) else { patchChgMatrix[y][x][2] = p; if (p > 0 && !existsPatch(p)) { - addPatchNum(p); + // addPatchNum(p); // was removed - why? newPatch(pchseq++, p); } } @@ -1465,6 +1712,13 @@ int Landscape::readLandChange(int filenum, bool costs) if (hfile.is_open()) { hfile.close(); hfile.clear(); } if (pfile.is_open()) { pfile.close(); pfile.clear(); } if (cfile.is_open()) { cfile.close(); cfile.clear(); } + + // add here the reading of demographic scaling layers + if(scalinglayers.size()>0){ + int retcode = readDemographicScaling(scalinglayers); + if (retcode < 0) return 54; //change number + } + return 0; } @@ -1472,7 +1726,6 @@ int Landscape::readLandChange(int filenum, bool costs) // Create & initialise patch change matrix void Landscape::createPatchChgMatrix(void) { - intptr patch; Patch* pPatch; Cell* pCell; if (patchChgMatrix != 0) deletePatchChgMatrix(); @@ -1487,12 +1740,11 @@ void Landscape::createPatchChgMatrix(void) } else { // record initial patch number - patch = pCell->getPatch(); - if (patch == 0) { // matrix cell + pPatch = pCell->getPatch(); + if (pPatch == nullptr) { // matrix cell patchChgMatrix[y][x][0] = patchChgMatrix[y][x][1] = 0; } else { - pPatch = (Patch*)patch; patchChgMatrix[y][x][0] = patchChgMatrix[y][x][1] = pPatch->getPatchNum(); } } @@ -1535,8 +1787,11 @@ void Landscape::recordPatchChanges(int landIx) { int Landscape::numPatchChanges(void) { return (int)patchchanges.size(); } patchChange Landscape::getPatchChange(int i) { - patchChange c; c.chgnum = 99999999; c.x = c.y = c.oldpatch = c.newpatch = -1; - if (i >= 0 && i < (int)patchchanges.size()) c = patchchanges[i]; + patchChange c; + c.chgnum = 99999999; + c.x = c.y = c.oldpatch = c.newpatch = -1; + if (i >= 0 && i < (int)patchchanges.size()) + c = patchchanges[i]; return c; } @@ -1555,6 +1810,8 @@ void Landscape::deletePatchChgMatrix(void) { // Create & initialise costs change matrix void Landscape::createCostsChgMatrix(void) { + //intptr patch; + //Patch *pPatch; Cell* pCell; if (costsChgMatrix != 0) deleteCostsChgMatrix(); costsChgMatrix = new int** [dimY]; @@ -1576,9 +1833,7 @@ void Landscape::createCostsChgMatrix(void) } void Landscape::recordCostChanges(int landIx) { -#if RSDEBUG - DEBUGLOG << "Landscape::recordCostChanges(): landIx=" << landIx << endl; -#endif + if (costsChgMatrix == 0) return; // should not occur costChange chg; @@ -1633,7 +1888,24 @@ void Landscape::deleteCostsChgMatrix(void) { // Species distribution functions -int Landscape::newDistribution(Species* pSpecies, string distname) { +//If file input as R objects only for #RS_RCPP +#if RS_RCPP +int Landscape::newDistribution(Species *pSpecies, Rcpp::NumericMatrix distname, int spResol) { + int readcode; + int ndistns = (int)distns.size(); + distns.push_back(new InitDist(pSpecies)); + landOrigin habfile_origin = this->getOrigin(); + readcode = distns[ndistns]->readDistribution(distname,habfile_origin,spResol); + if (readcode != 0) { // error encountered + // delete the distribution created above + delete distns[ndistns]; + distns.pop_back(); + } + return readcode; +} +#endif +// Standard file input +int Landscape::newDistribution(Species *pSpecies, string distname) { int readcode; int ndistns = (int)distns.size(); distns.push_back(new InitDist(pSpecies)); @@ -1646,6 +1918,7 @@ int Landscape::newDistribution(Species* pSpecies, string distname) { return readcode; } + void Landscape::setDistribution(Species* pSpecies, int nInit) { // WILL NEED TO SELECT DISTRIBUTION FOR CORRECT SPECIES ... // ... CURRENTLY IT IS THE ONLY ONE @@ -1664,10 +1937,6 @@ bool Landscape::inInitialDist(Species* pSpecies, locn loc) { } void Landscape::deleteDistribution(Species* pSpecies) { - // WILL NEED TO SELECT DISTRIBUTION FOR CORRECT SPECIES ... - // ... CURRENTLY IT IS THE ONLY ONE ... - // ... FOR MULTIPLE SPECIES IT MAY BE BETTER TO USE A DYNAMIC ARRAY FOR - // SPECIES DISTRIBUTIONS INDEXED BY SPECIES NUMBER, RATHER THAN A VECTOR if (distns[0] != 0) delete distns[0]; distns.clear(); } @@ -1741,367 +2010,663 @@ void Landscape::clearInitCells(void) { // Read landscape file(s) // Returns error code or zero if read correctly -int Landscape::readLandscape(int fileNum, string habfile, string pchfile, string costfile) -{ - // fileNum == 0 for (first) habitat file and optional patch file - // fileNum > 0 for subsequent habitat files under the %cover option - +// for new landscape input using R objects AND spatial demography: only for RS_RCPP +// RS_THREADSAFE and SPATIALDEMOG #if RS_RCPP - wstring header; -#else - string header; -#endif - int h, seq, p, habnodata; - int pchnodata = 0; - int ncols, nrows; - float hfloat, pfloat; - Patch* pPatch; - simParams sim = paramsSim->getSim(); - +int Landscape::readLandscape(int fileNum, Rcpp::NumericMatrix habfile, Rcpp::NumericMatrix pchfile, Rcpp::NumericMatrix costfile, Rcpp::NumericVector scalinglayers) { if (fileNum < 0) return 19; -#if RS_RCPP - wifstream hfile; // habitat file input stream - wifstream pfile; // patch file input stream -#else - ifstream hfile; // habitat file input stream - ifstream pfile; // patch file input stream -#endif + int habCode,seq,patchCode,ncols,nrows,hc,maxcost = 0; + double habFloat,patchFloat,costFloat; + Patch *pPatch; + Cell *pCell; + simParams sim = paramsSim->getSim(); initParams init = paramsInit->getInit(); - // open habitat file and optionally also patch file -#if !RS_RCPP || RSWIN64 - hfile.open(habfile.c_str()); -#else - hfile.open(habfile, std::ios::binary); - if (landraster.utf) { - // apply BOM-sensitive UTF-16 facet - hfile.imbue(std::locale(hfile.getloc(), new std::codecvt_utf16)); - } -#endif - if (!hfile.is_open()) return 11; - if (fileNum == 0) { - if (patchModel) { -#if !RS_RCPP || RSWIN64 - pfile.open(pchfile.c_str()); -#else - pfile.open(pchfile, std::ios::binary); - if (patchraster.utf) { - // apply BOM-sensitive UTF-16 facet - pfile.imbue(std::locale(pfile.getloc(), new std::codecvt_utf16)); - } -#endif - if (!pfile.is_open()) { - hfile.close(); hfile.clear(); - return 12; - } - } - } - -// read landscape data from header records of habitat file -// NB headers of all files have already been compared -double tmpresol; -hfile >> header >> ncols >> header >> nrows >> header >> minEast >> header >> minNorth - >> header >> tmpresol >> header >> habnodata; -resol = (int) tmpresol; - -#if RS_RCPP - if (!hfile.good()) { - // corrupt file stream - StreamErrorR(habfile); - hfile.close(); - hfile.clear(); - if (patchModel) { - pfile.close(); - pfile.clear(); - } - return 131; - } -#endif - - dimX = ncols; dimY = nrows; minX = maxY = 0; maxX = dimX - 1; maxY = dimY - 1; + // initialise landscape size + ncols = habfile.ncol(); + nrows = habfile.nrow(); + dimX = ncols; dimY = nrows; + minX = maxY = 0; + maxX = dimX-1; maxY = dimY-1; if (fileNum == 0) { // set initialisation limits to landscape limits init.minSeedX = init.minSeedY = 0; init.maxSeedX = maxX; init.maxSeedY = maxY; paramsInit->setInit(init); - } - - if (fileNum == 0) { - if (patchModel) { - for (int i = 0; i < 5; i++) pfile >> header >> pfloat; - pfile >> header >> pchnodata; - } -#if RS_RCPP - if (!pfile.good()) { - // corrupt file stream - StreamErrorR(pchfile); - hfile.close(); - hfile.clear(); - pfile.close(); - pfile.clear(); - return 135; - } -#endif setCellArray(); } - - // set up bad float values to ensure that valid values are read - float badhfloat = -9.0; if (habnodata == -9) badhfloat = -99.0; - float badpfloat = -9.0; if (pchnodata == -9) badpfloat = -99.0; - seq = 0; // initial sequential patch landscape - p = 0; // initial patch number for cell-based landscape + patchCode= 0; // initial patch number for cell-based landscape // create patch 0 - the matrix patch (even if there is no matrix) - if (fileNum == 0) newPatch(seq++, p++); + if (fileNum == 0) { + newPatch(seq,patchCode); + seq++; + patchCode++; + } switch (rasterType) { case 0: // raster with habitat codes - 100% habitat each cell if (fileNum > 0) return 19; // error condition - should not occur - for (int y = dimY - 1; y >= 0; y--) { + + for (int y = dimY-1; y >= 0; y--) { for (int x = 0; x < dimX; x++) { - hfloat = badhfloat; -#if RS_RCPP - if (hfile >> hfloat) { -#else - hfile >> hfloat; -#endif - h = (int)hfloat; - if (patchModel) { - pfloat = badpfloat; -#if RS_RCPP - if (pfile >> pfloat) { -#else - pfile >> pfloat; -#endif - p = (int)pfloat; -#if RS_RCPP - } + + // read value from raster cell + habFloat = habfile(dimY-1-y,x); + // check for NA + if ( R_IsNA(habFloat) ) + addNewCellToLand(x,y,-1); // add cell only to landscape else { - // corrupt file stream -#if RS_RCPP && !R_CMD - Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; -#endif - StreamErrorR(pchfile); - hfile.close(); - hfile.clear(); - pfile.close(); - pfile.clear(); - return 132; + habCode= static_cast(habFloat); + if (habCode< 0 || (sim.batchMode && (habCode< 1 || habCode> nHabMax))) { + // invalid habitat code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found invalid habitat code." << std::endl; + #endif + return 13; + } + else { // valid habitat code + addHabCode(habCode); + if (patchModel) { + patchFloat = pchfile(dimY-1-y,x); + if ( R_IsNA(patchFloat) ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + #endif + return 14; + } + patchCode= static_cast(patchFloat); + if (patchCode< 0 ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; + #endif + return 14; + } + + // Does the patch already exists? + pPatch = patchCode == 0 ? nullptr : ( // matrix cell + existsPatch(patchCode) ? + findPatch(patchCode) : + newPatch(seq++, patchCode) + ); + addNewCellToPatch(pPatch, x, y, habCode); + } + else { // cell-based model + // add cell to landscape (patches created later) + addNewCellToLand(x,y,habCode); + } + } + } // end of habFloat is NA + } // end x + } // end y + break; + + case 1: // multiple % cover + + for (int y = dimY-1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + + habFloat = habfile(dimY-1-y,x); + if (fileNum == 0) { // first habitat cover layer + if ( R_IsNA(habFloat) ) { // check for NA + addNewCellToLand(x,y,-1); // add cell only to landscape + } + else { + if (habFloat < 0.0 || habFloat > 100.0) { // invalid cover score + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found invalid habitat cover score." << std::endl; + #endif + return 17; + } + else { + if (patchModel) { + patchFloat = pchfile(dimY-1-y,x); + if ( R_IsNA(patchFloat) ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + #endif + return 14; + } + + patchCode= static_cast(patchFloat); + + if (patchCode< 0 ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; + #endif + return 14; + } + + pPatch = patchCode == 0 ? nullptr : ( // matrix cell + existsPatch(patchCode) ? + findPatch(patchCode) : + newPatch(seq++, patchCode) + ); + addNewCellToPatch(pPatch, x, y, (float)habFloat); + } + else { // cell-based model + // add cell to landscape (patches created later) + addNewCellToLand(x,y,(float)habFloat); + } + } + } } -#endif + else { // additional habitat cover layers + if ( !R_IsNA(habFloat) ) { + if (habFloat < 0.0 || habFloat > 100.0) { // invalid cover score + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found invalid habitat cover score." << std::endl; + #endif + return 17; + } + else { + cells[dimY-1-y][x]->setHabitat((float)habFloat); + } + } // end of habFloat is not NA + } // end additional habitat cover layers + } // end x + } // end y + habIndexed = true; // habitats are already numbered 1...n in correct order + + break; + + case 2: // habitat quality + if (fileNum > 0) return 19; // error condition - should not occur + + for (int y = dimY-1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + habFloat = habfile(dimY-1-y,x); + if ( R_IsNA(habFloat) ) { // check for NA + addNewCellToLand(x,y,-1); // add cell only to landscape } -#if RS_RCPP + else { + if (habFloat < 0.0 || habFloat > 100.0) { // invalid quality score + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found invalid habitat quality score." << std::endl; + #endif + return 17; + } + else { // valid quality score + if (patchModel) { + patchFloat = pchfile(dimY-1-y,x); + if ( R_IsNA(patchFloat) ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + #endif + return 14; + } + patchCode= static_cast(patchFloat); + if (patchCode< 0 ) { // invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; + #endif + return 14; + } + + pPatch = patchCode == 0 ? nullptr : ( // matrix cell + existsPatch(patchCode) ? + findPatch(patchCode) : + newPatch(seq++, patchCode) + ); + addNewCellToPatch(pPatch, x, y, (float)habFloat); + } + else { // cell-based model + // add cell to landscape (patches created later) + addNewCellToLand(x,y,(float)habFloat); + } + } // end of valid quality score + } // end of habFloat is not NA + } // end x + } // end y + break; + + default: + break; + } // end switch(rasterType) + +//#if SPATIALDEMOG + int SMScosts = costfile.nrow()*costfile.ncol(); + + arma::vec cellDemoScalings; // vector to store local demog scalings + Rcpp::IntegerVector DSdim; + int nrDemogScaleLayers = 0; + if(scalinglayers.attr("dim")==R_NilValue) DSdim = Rcpp::IntegerVector::create(1,1,1); + else{ + DSdim = scalinglayers.attr("dim"); + if(DSdim.size()>2) nrDemogScaleLayers = DSdim[2]; //nr of slices on cube + else nrDemogScaleLayers = 1; + } + arma::cube scalingCube(scalinglayers.begin(),DSdim[0],DSdim[1],nrDemogScaleLayers,false); // turn scaling layers into a cube, what happens if it is not a spatial demog? + + if(SMScosts || nrDemogScaleLayers){ // are there SMS costs or demographic scaling layers to read? + + for (int y = dimY-1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + + // find the cell + pCell = findCell(x,y); + if (pCell != 0) { // not no-data cell + + // read cost raster + if(SMScosts) { + costFloat = costfile(dimY-1-y,x); + if ( !R_IsNA(costFloat) ) { + hc = (int)costFloat; + if ( hc < 1 ) { + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Cost map may only contain values of 1 or higher, but found " << hc << "." << endl; + #endif + return 54; + } + // set cost value + pCell->setCost(hc); + if (hc > maxcost) maxcost = hc; + } + } + + // read demographic scalings + if(nrDemogScaleLayers){ + // get tube at (y/x) + cellDemoScalings = scalingCube(arma::span(dimY-1-y), arma::span(x), arma::span::all); + if(cellDemoScalings.n_elem==(unsigned)nDSlayer){ + // set vector percentage values in cell + pCell->addchgDemoScaling(arma::conv_to< std::vector >::from(cellDemoScalings)); + } + else{// invalid patch code + #if RS_RCPP && !R_CMD + Rcpp::Rcout << "Wrong number of demographic scaling layers in cell " << x << " ," << y << " of first layer array." << std::endl; + #endif + return 64; + } + } } - else { - // corrupt file stream -#if RS_RCPP && !R_CMD - Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; + } + } + } + return 0; +} #endif - StreamErrorR(habfile); - hfile.close(); - hfile.clear(); - if (patchModel) { - pfile.close(); - pfile.clear(); + +int Landscape::readLandscape(int fileNum, string habfile, string pchfile, string costfile, vector scalinglayers) // vector scalinglayers is the vector of filenames for spatial demography of year 1 +{ + // fileNum == 0 for (first) habitat file and optional patch file + // fileNum > 0 for subsequent habitat files under the %cover option + +#if RS_RCPP + wstring header; +#else + string header; +#endif + int habCode, seq, patchCode, noDataHabCode; + int noDataPatch = 0; + int ncols, nrows; + float habFloat, patchFloat; + Patch* pPatch; + simParams sim = paramsSim->getSim(); + + if (fileNum < 0) return 19; + +#if RS_RCPP + wifstream ifsHabMap; // habitat file input stream + wifstream ifsPatchMap; // patch file input stream +#else + ifstream ifsHabMap; // habitat file input stream + ifstream ifsPatchMap; // patch file input stream +#endif + initParams init = paramsInit->getInit(); + + // Open habitat file and optionally also patch file +// #if RS_RCPP +// hfile.open(habfile, std::ios::binary); +// if (landraster.utf) { +// // apply BOM-sensitive UTF-16 facet +// hfile.imbue(std::locale(hfile.getloc(), new std::codecvt_utf16)); +// } +// #else + ifsHabMap.open(habfile.c_str()); +// #endif +// #endif + + if (!ifsHabMap.is_open()) return 11; + + if (fileNum == 0) { + if (patchModel) { +// #if RS_RCPP +// pfile.open(pchfile, std::ios::binary); +// if (patchraster.utf) { +// // apply BOM-sensitive UTF-16 facet +// pfile.imbue(std::locale(pfile.getloc(), new std::codecvt_utf16)); +// } +// #else + ifsPatchMap.open(pchfile.c_str()); +// #endif +// #endif + if (!ifsPatchMap.is_open()) { + ifsHabMap.close(); + ifsHabMap.clear(); + return 12; } + } + } + +// read landscape data from header records of habitat file +// NB headers of all files have already been compared +// !! DO NOT REMOVE THIS WORKAROUND !! NEEDED FOR LANDSCAPES GENERATED BY TERRA (R PACKAGE) +double tmpresol; + ifsHabMap >> header >> ncols + >> header >> nrows + >> header >> minEast + >> header >> minNorth + >> header >> tmpresol + >> header >> noDataHabCode; +resol = (int) tmpresol; +#if RS_RCPP + if (!ifsHabMap.good()) { + // corrupt file stream + StreamErrorR(habfile); + ifsHabMap.close(); + ifsHabMap.clear(); + if (patchModel) { + ifsPatchMap.close(); + ifsPatchMap.clear(); + } + return 131; + } +#endif + + dimX = ncols; + dimY = nrows; + minX = maxY = 0; // bottom-left / south-west corner + maxX = dimX - 1; + maxY = dimY - 1; + + if (fileNum == 0) { // First map layer + + // Set initialisation limits to landscape limits + init.minSeedX = init.minSeedY = 0; + init.maxSeedX = maxX; + init.maxSeedY = maxY; + paramsInit->setInit(init); + + if (patchModel) { + for (int i = 0; i < 5; i++) + ifsPatchMap >> header >> patchFloat; + ifsPatchMap >> header >> noDataPatch; + } + +#if RS_RCPP + if (!ifsPatchMap.good()) { + // corrupt file stream + StreamErrorR(pchfile); + ifsHabMap.close(); + ifsHabMap.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); return 135; } #endif - if (h == habnodata) - addNewCellToLand(x, y, -1); // add cell only to landscape - else { + setCellArray(); + } - // THERE IS AN ANOMALY HERE - CURRENTLY HABITAT 0 IS OK FOR GUI VERSION BUT - // NOT ALLOWED FOR BATCH VERSION (HABITATS MUST BE 1...n) - // SHOULD WE MAKE THE TWO VERSIONS AGREE? ... + // set up bad float values to ensure that valid values are read + float badHabFloat = -9.0; + if (noDataHabCode == -9) badHabFloat = -99.0; + float badPatchFloat = -9.0; + if (noDataPatch == -9) badPatchFloat = -99.0; - if (h < 0 || (sim.batchMode && (h < 1 || h > nHabMax))) { - // invalid habitat code -#if RS_RCPP && !R_CMD - Rcpp::Rcout << "Found invalid habitat code." << std::endl; + seq = 0; // initial sequential patch landscape + patchCode = 0; // initial patch number for cell-based landscape + + // Create the matrix patch (even if there is no matrix) + if (fileNum == 0) { + newPatch(seq, patchCode); + seq++; + patchCode++; + } + + switch (rasterType) { + + case 0: // Raster with habitat codes - 100% habitat each cell + if (fileNum > 0) return 19; // error condition - should not occur + + for (int y = dimY - 1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + + habFloat = badHabFloat; +#if RS_RCPP + if (ifsHabMap >> habFloat) { + habCode = (int)habFloat; + if (patchModel) { + patchFloat = badPatchFloat; + if (ifsPatchMap >> patchFloat) { + patchCode = (int)patchFloat; + } + else { // corrupt file stream +#if !R_CMD + Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; +#endif + StreamErrorR(pchfile); + ifsHabMap.close(); + ifsHabMap.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); + return 132; + } + } + } + else { // corrupt file stream +#if !R_CMD + Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; #endif - hfile.close(); hfile.clear(); + StreamErrorR(habfile); + ifsHabMap.close(); + ifsHabMap.clear(); + if (patchModel) { + ifsPatchMap.close(); + ifsPatchMap.clear(); + } + return 135; + } +#else + // Read habitat code in this cell + ifsHabMap >> habFloat; + habCode = static_cast(habFloat); if (patchModel) { - pfile.close(); pfile.clear(); + // Read patch code in this cell + patchFloat = badPatchFloat; + ifsPatchMap >> patchFloat; + patchCode = static_cast(patchFloat); } - return 13; - } - else { - addHabCode(h); - if (patchModel) { - if (p < 0 || p == pchnodata) { // invalid patch code +#endif + if (habCode == noDataHabCode) { + // No-data cell + addNewCellToLand(x, y, -1); // x, y is a null cell + } + else if (habCode < 0 || (sim.batchMode && (habCode < 1 || habCode > nHabMax))) { + // Invalid habitat code #if RS_RCPP && !R_CMD - if (p == pchnodata) Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; - else Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; + Rcpp::Rcout << "Found invalid habitat code." << std::endl; #endif - hfile.close(); hfile.clear(); - pfile.close(); pfile.clear(); - return 14; - } - if (p == 0) { // cell is in the matrix - addNewCellToPatch(0, x, y, h); + ifsHabMap.close(); + ifsHabMap.clear(); + if (patchModel) { + ifsPatchMap.close(); + ifsPatchMap.clear(); } - else { - if (existsPatch(p)) { - pPatch = findPatch(p); - addNewCellToPatch(pPatch, x, y, h); - // addNewCellToPatch(findPatch(p),x,y,h); - } - else { - pPatch = newPatch(seq++, p); - addNewCellToPatch(pPatch, x, y, h); + return 13; + } + else { // Valid habitat code + addHabCode(habCode); + if (patchModel) { + if (patchCode < 0 || patchCode == noDataPatch) { // invalid patch code +#if RS_RCPP && !R_CMD + if (patchCode == noDataPatch) Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + else Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; +#endif + ifsHabMap.close(); + ifsHabMap.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); + return 14; } + // Does the patch already exists? + pPatch = patchCode == 0 ? nullptr : ( // matrix cell + existsPatch(patchCode) ? + findPatch(patchCode) : + newPatch(seq++, patchCode) + ); + addNewCellToPatch(pPatch, x, y, habCode); + } + else { // cell-based model + // add cell to landscape (patches created later) + addNewCellToLand(x, y, habCode); } } - else { // cell-based model - // add cell to landscape (patches created later) - addNewCellToLand(x, y, h); - } - } - } - } - } + + } // for x + } // for y + #if RS_RCPP -hfile >> hfloat; -if (!hfile.eof()) EOFerrorR(habfile); -if (patchModel) -{ - pfile >> pfloat; - if (!pfile.eof()) EOFerrorR(pchfile); -} + ifsHabMap >> habFloat; + if (!ifsHabMap.eof()) EOFerrorR(habfile); + if (patchModel) { + ifsPatchMap >> patchFloat; + if (!ifsPatchMap.eof()) EOFerrorR(pchfile); + } #endif break; case 1: // multiple % cover for (int y = dimY - 1; y >= 0; y--) { for (int x = 0; x < dimX; x++) { - hfloat = badhfloat; -#if RS_RCPP - if (hfile >> hfloat) { -#else - hfile >> hfloat; -#endif - h = (int)hfloat; - if (fileNum == 0) { // first habitat cover layer - if (patchModel) { - pfloat = badpfloat; -#if RS_RCPP - if (pfile >> pfloat) { -#else - pfile >> pfloat; -#endif - p = (int)pfloat; + + habFloat = badHabFloat; #if RS_RCPP + if (ifsHabMap >> habFloat) { + habCode = static_cast(habFloat); + if (fileNum == 0) { // first habitat cover layer + if (patchModel) { + patchFloat = badPatchFloat; + if (ifsPatchMap >> patchFloat) { + patchCode = static_cast(patchFloat); } else { // corrupt file stream -#if RS_RCPP && !R_CMD +#if !R_CMD Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; #endif - StreamErrorR(pchfile); - hfile.close(); - hfile.clear(); - pfile.close(); - pfile.clear(); + StreamErrorR(pchfile); + ifsHabMap.close(); + ifsHabMap.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); return 135; } + } // end if patchModel +#else + ifsHabMap >> habFloat; + habCode = static_cast(habFloat); + + if (fileNum == 0) { // first habitat cover layer + if (patchModel) { + patchFloat = badPatchFloat; + ifsPatchMap >> patchFloat; + patchCode = static_cast(patchFloat); + } // end if patchModel #endif - } //end if patchmodel - if (h == habnodata) { + if (habCode == noDataHabCode) { addNewCellToLand(x, y, -1); // add cell only to landscape } - else { - if (hfloat < 0.0 || hfloat > 100.0) { // invalid cover score + else if (habFloat < 0.0 || habFloat > 100.0) { // invalid cover score #if RS_RCPP && !R_CMD Rcpp::Rcout << "Found invalid habitat cover score." << std::endl; #endif - hfile.close(); hfile.clear(); - if (patchModel) { - pfile.close(); pfile.clear(); + ifsHabMap.close(); + ifsHabMap.clear(); + if (patchModel) { + ifsPatchMap.close(); + ifsPatchMap.clear(); + } + return 17; } - return 17; - } - else { - if (patchModel) { - if (p < 0 || p == pchnodata) { // invalid patch code + else { + if (patchModel) { + if (patchCode < 0 || patchCode == noDataPatch) { // invalid patch code #if RS_RCPP && !R_CMD - if (p == pchnodata) Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + if (patchCode == noDataPatch) Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; else Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; #endif - hfile.close(); hfile.clear(); - pfile.close(); pfile.clear(); - return 14; - } - if (p == 0) { // cell is in the matrix - addNewCellToPatch(0, x, y, hfloat); - } - else { - if (existsPatch(p)) { - pPatch = findPatch(p); - addNewCellToPatch(pPatch, x, y, hfloat); - // addNewCellToPatch(findPatch(p),x,y,hfloat); - } - else { - pPatch = newPatch(seq++, p); - addNewCellToPatch(pPatch, x, y, hfloat); + ifsHabMap.close(); + ifsHabMap.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); + return 14; } + + pPatch = patchCode == 0 ? nullptr : ( // matrix cell + existsPatch(patchCode) ? + findPatch(patchCode) : + newPatch(seq++, patchCode) + ); + addNewCellToPatch(pPatch, x, y, habFloat); + } // end patchModel + else { // cell-based model + // add cell to landscape (patches created later) + addNewCellToLand(x, y, habFloat); } - } - else { // cell-based model - // add cell to landscape (patches created later) - addNewCellToLand(x, y, hfloat); - } + } // if valid habFloat } - } - } else { // additional habitat cover layers - if (h != habnodata) { - if (hfloat < 0.0 || hfloat > 100.0) { // invalid cover score + if (habCode != noDataHabCode) { + if (habFloat < 0.0 || habFloat > 100.0) { // invalid cover score #if RS_RCPP && !R_CMD Rcpp::Rcout << "Found invalid habitat cover score." << std::endl; #endif - hfile.close(); hfile.clear(); + ifsHabMap.close(); + ifsHabMap.clear(); if (patchModel) { - pfile.close(); pfile.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); } return 17; } else { - cells[y][x]->setHabitat(hfloat); - } - } // end of h != habnodata -} + cells[y][x]->setHabitat(habFloat); + } + } + } #if RS_RCPP - } -else { // couldn't read from hfile + } + else { // not ifsHabMap >> habFloat // corrupt file stream #if RS_RCPP && !R_CMD Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; #endif - StreamErrorR(habfile); - hfile.close(); - hfile.clear(); - if (patchModel) { - pfile.close(); - pfile.clear(); + StreamErrorR(habfile); + ifsHabMap.close(); + ifsHabMap.clear(); + if (patchModel) { + ifsPatchMap.close(); + ifsPatchMap.clear(); } return 133; -} + }// end not ifsHabMap >> habFloat #endif - } - } + } // for x + } // for y + habIndexed = true; // habitats are already numbered 1...n in correct order + #if RS_RCPP - hfile >> hfloat; - if (!hfile.eof()) EOFerrorR(habfile); - if (patchModel) - { - pfile >> pfloat; - if (!pfile.eof()) EOFerrorR(pchfile); + ifsHabMap >> habFloat; + if (!ifsHabMap.eof()) EOFerrorR(habfile); + if (patchModel) { + ifsPatchMap >> patchFloat; + if (!ifsPatchMap.eof()) EOFerrorR(pchfile); } #endif break; @@ -2110,110 +2675,111 @@ case 2: // habitat quality if (fileNum > 0) return 19; // error condition - should not occur for (int y = dimY - 1; y >= 0; y--) { for (int x = 0; x < dimX; x++) { - hfloat = badhfloat; -#if RS_RCPP - if (hfile >> hfloat) { -#else - hfile >> hfloat; -#endif - h = (int)hfloat; + + habFloat = badHabFloat; #if RS_RCPP + if (ifsHabMap >> habFloat) { + habCode = static_cast(habFloat); + } else { // corrupt file stream -#if RS_RCPP && !R_CMD +#if !R_CMD Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; #endif - StreamErrorR(habfile); - hfile.close(); - hfile.clear(); - if (patchModel) { - pfile.close(); - pfile.clear(); + StreamErrorR(habfile); + ifsHabMap.close(); + ifsHabMap.clear(); + if (patchModel) { + ifsPatchMap.close(); + ifsPatchMap.clear(); } return 134; } -#endif if (patchModel) { - pfloat = badpfloat; -#if RS_RCPP - if (pfile >> pfloat) { -#else - pfile >> pfloat; -#endif - p = (int)pfloat; -#if RS_RCPP + patchFloat = badPatchFloat; + if (ifsPatchMap >> patchFloat) { + patchCode = static_cast(patchFloat); } else { // corrupt file stream #if RS_RCPP && !R_CMD Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; #endif - StreamErrorR(pchfile); - hfile.close(); - hfile.clear(); - pfile.close(); - pfile.clear(); + StreamErrorR(pchfile); + ifsHabMap.close(); + ifsHabMap.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); return 135; } + } +#else + ifsHabMap >> habFloat; + habCode = static_cast(habFloat); + if (patchModel) { + patchFloat = badPatchFloat; + ifsPatchMap >> patchFloat; + patchCode = static_cast(patchFloat); + } #endif - } - if (h == habnodata) { + if (habCode == noDataHabCode) { addNewCellToLand(x, y, -1); // add cell only to landscape } - else { - if (hfloat < 0.0 || hfloat > 100.0) { // invalid quality score + else if (habFloat < 0.0 || habFloat > 100.0) { // invalid quality score #if RS_RCPP && !R_CMD Rcpp::Rcout << "Found invalid habitat quality score." << std::endl; #endif - hfile.close(); hfile.clear(); - if (patchModel) { - pfile.close(); pfile.clear(); - } - return 17; - } - else { - if (patchModel) { - if (p < 0 || p == pchnodata) { // invalid patch code + ifsHabMap.close(); + ifsHabMap.clear(); + if (patchModel) { + ifsPatchMap.close(); + ifsPatchMap.clear(); + } + return 17; + } + else { + if (patchModel) { + if (patchCode < 0 || patchCode == noDataPatch) { // invalid patch code #if RS_RCPP && !R_CMD - if (p == pchnodata) Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; + if (patchCode == noDataPatch) Rcpp::Rcout << "Found patch NA in valid habitat cell." << std::endl; else Rcpp::Rcout << "Found negative patch ID in valid habitat cell." << std::endl; #endif - hfile.close(); hfile.clear(); - pfile.close(); pfile.clear(); - return 14; - } - if (p == 0) { // cell is in the matrix - addNewCellToPatch(0, x, y, hfloat); + ifsHabMap.close(); + ifsHabMap.clear(); + ifsPatchMap.close(); + ifsPatchMap.clear(); + return 14; + } + if (patchCode == 0) { // cell is in the matrix + addNewCellToPatch(0, x, y, habFloat); } else { - if (existsPatch(p)) { - pPatch = findPatch(p); - addNewCellToPatch(pPatch, x, y, hfloat); - // addNewCellToPatch(findPatch(p),x,y,hfloat); + if (existsPatch(patchCode)) { + pPatch = findPatch(patchCode); } else { - addPatchNum(p); - pPatch = newPatch(seq++, p); - addNewCellToPatch(pPatch, x, y, hfloat); + pPatch = newPatch(seq++, patchCode); } + addNewCellToPatch(pPatch, x, y, habFloat); } - } - else { // cell-based model - // add cell to landscape (patches created later) - addNewCellToLand(x, y, hfloat); - } - } - } - } - } + } + else { // cell-based model + // add cell to landscape (patches created later) + addNewCellToLand(x, y, habFloat); + } + } + + } // for x + } // for y + #if RS_RCPP - hfile >> hfloat; - if (!hfile.eof()) EOFerrorR(habfile); - if (patchModel) - { - pfile >> pfloat; - if (!pfile.eof()) EOFerrorR(pchfile); + ifsHabMap >> habFloat; + if (!ifsHabMap.eof()) EOFerrorR(habfile); + if (patchModel) + { + ifsPatchMap >> patchFloat; + if (!ifsPatchMap.eof()) EOFerrorR(pchfile); } #endif break; @@ -2222,18 +2788,29 @@ case 2: // habitat quality break; } // end switch(rasterType) - if (hfile.is_open()) { hfile.close(); hfile.clear(); } - if (pfile.is_open()) { pfile.close(); pfile.clear(); } + if (ifsHabMap.is_open()) { + ifsHabMap.close(); + ifsHabMap.clear(); + } + if (ifsPatchMap.is_open()) { + ifsPatchMap.close(); + ifsPatchMap.clear(); + } if (sim.batchMode) { if (costfile != "NULL") { int retcode = readCosts(costfile); if (retcode < 0) return 54; + } + if (scalinglayers.size() > 0) { + if (scalinglayers.size() == nDSlayer) { + int retcode = readDemographicScaling(scalinglayers); + if (retcode < 0) return 54; //change number + } } } return 0; - } //--------------------------------------------------------------------------- @@ -2261,22 +2838,19 @@ int Landscape::readCosts(string fname) string header; #endif Cell* pCell; -#if !RS_RCPP - simView v = paramsSim->getViews(); -#endif int maxcost = 0; // open cost file -#if !RS_RCPP || RSWIN64 +// #if RS_RCPP +// costs.open(fname, std::ios::binary); +// if (costsraster.utf) { +// // apply BOM-sensitive UTF-16 facet +// costs.imbue(std::locale(costs.getloc(), new std::codecvt_utf16)); +// } +// #else costs.open(fname.c_str()); -#else - costs.open(fname, std::ios::binary); - if (costsraster.utf) { - // apply BOM-sensitive UTF-16 facet - costs.imbue(std::locale(costs.getloc(), new std::codecvt_utf16)); - } -#endif +// #endif // read headers and check that they correspond to the landscape ones costs >> header; #if RS_RCPP @@ -2291,10 +2865,6 @@ int Landscape::readCosts(string fname) #else if (header != "ncols" && header != "NCOLS") { #endif - -// MessageDlg("The selected file is not a raster.", -// MessageDlg("Header problem in import_CostsLand()", -// mtError, TMsgDlgButtons() << mbRetry,0); costs.close(); costs.clear(); return -1; } @@ -2303,10 +2873,6 @@ costs >> maxXcost >> header >> maxYcost >> header >> minLongCost; costs >> header >> minLatCost >> header >> tmpresolCost >> header >> NODATACost; resolCost = (int) tmpresolCost; -#if !RS_RCPP - MemoLine("Loading costs map. Please wait..."); -#endif - for (int y = maxYcost - 1; y > -1; y--) { for (int x = 0; x < maxXcost; x++) { #if RS_RCPP @@ -2343,13 +2909,12 @@ resolCost = (int) tmpresolCost; if (hc > maxcost) maxcost = hc; } else { // if cost value is below 0 #if RS_RCPP && !R_CMD - Rcpp::Rcout << "Cost map may only contain values of 1 or higher in habiat cells, but found " << hc << " in cell x: " << x << " y: " << y << "." << endl; + Rcpp::Rcout << "Cost map may only contain values of 1 or higher in habitat cells, but found " << hc << " in cell x: " << x << " y: " << y << "." << endl; #endif - // costs.close(); costs.clear(); // not sure if it should stop at this point - // return -999; + throw runtime_error("Found negative- or zero-cost habitat cell."); } - } // end not no data vell + } // end not no data cell } } #if RS_RCPP @@ -2360,8 +2925,6 @@ if (costs.eof()) { #endif } else EOFerrorR(fname); -#else -MemoLine("Costs map loaded."); #endif costs.close(); costs.clear(); @@ -2370,6 +2933,134 @@ return maxcost; } + //--------------------------------------------------------------------------- + int Landscape::readDemographicScaling(vector scalinglayers){ + // Create a temporary landscape to store the vectors for each cell + // I bet there is a better way to implement it, but I couldn't think of it for now + // each cell will contain a vector of three floats + std::vector>> landscape(dimY, + std::vector>(dimX, + std::vector(scalinglayers.size(), 0.0f))); //length of string is determined by DSlayer + +#if RS_RCPP + wstring header; +#else + string header; +#endif + + int DSnb = 0; // first position is 0 + int DS; + int DSnodata; + float DSfloat; // float for reading in data from file + +#if RS_RCPP + wifstream DSfile; // DS file input stream +#else + ifstream DSfile; // DS file input stream +#endif + + + + // for each element of scalinglayers + for (const auto& DSlayer : scalinglayers) { //DSlayer is a reference to file string + // open file + DSfile.open(DSlayer.c_str()); + // if file couldn't be opened, return a failure message (and the readLandscape function: close all file connections and exit) + if (!DSfile.is_open()) return -11; // might need to change the code number + + // if it opened + // skip header, but store no data value + for (int i = 0; i < 5; i++) + DSfile >> header >> DSfloat; + DSfile >> header >> DSnodata; // 6th line + +#if RS_RCPP + if (!DSfile.good()) { + // corrupt file stream + StreamErrorR(DSlayer); + DSfile.close(); + DSfile.clear(); + //do I also need to close the habitat, and potentially patchfile? or is this taken care of? + return -181; // need to change the code + } +#endif + + // set badfloat + float badDSfloat = -9.0; if (DSnodata == -9) DSfloat = -99.0; + + // loop over landscape x+y + for (int y = dimY - 1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + // read in cell value + DSfloat = badDSfloat; // set to bad float value + // do I need to check this value for any inconsistencies? -> better safe than sorry +#if RS_RCPP + if (DSfile >> DSfloat) { +#else + DSfile >> DSfloat; +#endif + // DS = (int)DSfloat; I guess non integer values are totally fine? +#if RS_RCPP + } + else { + // corrupt file stream +#if RS_RCPP && !R_CMD + Rcpp::Rcout << "At (x,y) = " << x << "," << y << " :" << std::endl; +#endif + StreamErrorR(DSlayer); + DSfile.close(); + DSfile.clear(); + return -134; // need to change number code + } +#endif + // check if the value is a no data value + if (DSfloat == DSnodata) { + // if it is, do I need to do anything? + } + else { + if (DSfloat < 0.0 || DSfloat > 100.0) { // check for negative values or values above 100 +#if RS_RCPP && !R_CMD + Rcpp::Rcout << "Found invalid demographic scaling score." << std::endl; +#endif + DSfile.close(); DSfile.clear(); + return -17; // need to change code + } + else { + // if the value is ok, + // set the vector value of this cell at this location to the float value read in: + landscape[y][x][DSnb] = DSfloat; // Assignment to specific cell's vector + } + } + } // end x loop + } // end y loop + + // use Rcpp::Rcout to print the landscape at DSnb=0: + // close file connection +#if RS_RCPP + DSfile >> DSfloat; + if (!DSfile.eof()) EOFerrorR(DSlayer); + else { +#if RS_RCPP && !R_CMD + Rcpp::Rcout << "Demographic scaling layer " << DSnb + 1 << " loaded." << endl; +#endif + } +#endif + if (DSfile.is_open()) { DSfile.close(); DSfile.clear(); } + DSnb ++; // increase the reference number and read in next file + }; // end loop over scalinglayers + + // add the cells' vector of DSfloats to the actual landscape using the function addchgDemoScaling + // loop over all cells + for (int y = dimY - 1; y >= 0; y--) { + for (int x = 0; x < dimX; x++) { + // extract the vector in landscape at x, y + std::vector localDS = landscape[y][x]; + cells[y][x]->addchgDemoScaling(localDS); // add the vector to the cell + } + } + return 0; // return success code + }; + //--------------------------------------------------------------------------- rasterdata CheckRasterFile(string fname) @@ -2386,42 +3077,18 @@ rasterdata CheckRasterFile(string fname) infile.open(fname.c_str()); if (infile.is_open()) { infile >> header >> r.ncols; -#if RSDEBUG - DebugGUI(("CheckRasterFile(): header=" + header + " r.ncols=" + Int2Str(r.ncols) - ).c_str()); -#endif if (header != "ncols" && header != "NCOLS") r.errors++; infile >> header >> r.nrows; -#if RSDEBUG - DebugGUI(("CheckRasterFile(): header=" + header + " r.nrows=" + Int2Str(r.nrows) - ).c_str()); -#endif if (header != "nrows" && header != "NROWS") r.errors++; infile >> header >> r.xllcorner; -#if RSDEBUG - DebugGUI(("CheckRasterFile(): header=" + header + " r.xllcorner=" + Float2Str(r.xllcorner) - ).c_str()); -#endif if (header != "xllcorner" && header != "XLLCORNER") r.errors++; infile >> header >> r.yllcorner; -#if RSDEBUG - DebugGUI(("CheckRasterFile(): header=" + header + " r.yllcorner=" + Float2Str(r.yllcorner) - ).c_str()); -#endif if (header != "yllcorner" && header != "YLLCORNER") r.errors++; double tmpcellsize; infile >> header >> tmpcellsize; r.cellsize = (int) tmpcellsize; -#if RSDEBUG - DebugGUI(("CheckRasterFile(): header=" + header + " r.cellsize=" + Int2Str(r.cellsize) - ).c_str()); -#endif if (header != "cellsize" && header != "CELLSIZE") r.errors++; infile >> header >> inint; -#if RSDEBUG - DebugGUI(("CheckRasterFile(): header=" + header + " inint=" + Int2Str(inint) - ).c_str()); -#endif if (header != "NODATA_value" && header != "NODATA_VALUE") r.errors++; infile.close(); infile.clear(); @@ -2444,9 +3111,6 @@ void Landscape::createConnectMatrix(void) { if (connectMatrix != 0) deleteConnectMatrix(); int npatches = (int)patches.size(); -#if RSDEBUG - //DEBUGLOG << "Landscape::createConnectMatrix(): npatches=" << npatches << endl; -#endif connectMatrix = new int* [npatches]; for (int i = 0; i < npatches; i++) { connectMatrix[i] = new int[npatches]; @@ -2486,24 +3150,26 @@ void Landscape::deleteConnectMatrix(void) } } -// Write connectivity file headers -bool Landscape::outConnectHeaders(int option) +// Close connectivity file +bool Landscape::outConnectFinishLandscape() { - if (option == -999) { // close the file if (outConnMat.is_open()) outConnMat.close(); outConnMat.clear(); return true; } +// Open connectivity file and write header record +bool Landscape::outConnectStartLandscape() +{ simParams sim = paramsSim->getSim(); string name = paramsSim->getDir(2); if (sim.batchMode) { - name += "Batch" + Int2Str(sim.batchNum) + "_"; - name += "Sim" + Int2Str(sim.simulation) + "_Land" + Int2Str(landNum); + name += "Batch" + to_string(sim.batchNum) + "_"; + name += "Sim" + to_string(sim.simulation) + "_Land" + to_string(landNum); } else - name += "Sim" + Int2Str(sim.simulation); + name += "Sim" + to_string(sim.simulation); name += "_Connect.txt"; outConnMat.open(name.c_str()); @@ -2513,26 +3179,27 @@ bool Landscape::outConnectHeaders(int option) } #if RS_RCPP -// Write movement paths file headers -void Landscape::outPathsHeaders(int rep, int option) +// Close movement paths file +void Landscape::outPathsFinishReplicate() { - if (option == -999) { // close the file if (outMovePaths.is_open()) outMovePaths.close(); outMovePaths.clear(); } - if (option == 0) { // open the file and write header +// Open movement paths file and write header record +void Landscape::outPathsStartReplicate(int rep) +{ simParams sim = paramsSim->getSim(); string name = paramsSim->getDir(2); if (sim.batchMode) { - name += "Batch" + Int2Str(sim.batchNum) - + "_Sim" + Int2Str(sim.simulation) - + "_Land" + Int2Str(landNum) - + "_Rep" + Int2Str(rep); + name += "Batch" + to_string(sim.batchNum) + + "_Sim" + to_string(sim.simulation) + + "_Land" + to_string(landNum) + + "_Rep" + to_string(rep); } else { - name += "Sim" + Int2Str(sim.simulation) - + "_Rep" + Int2Str(rep); + name += "Sim" + to_string(sim.simulation) + + "_Rep" + to_string(rep); } name += "_MovePaths.txt"; @@ -2541,11 +3208,7 @@ void Landscape::outPathsHeaders(int rep, int option) outMovePaths << "Year\tIndID\tStep\tx\ty\tStatus" << endl; } else { -#if RSDEBUG - DEBUGLOG << "RunModel(): UNABLE TO OPEN MOVEMENT PATHS FILE" << endl; -#endif outMovePaths.clear(); - } } } #endif @@ -2618,20 +3281,20 @@ void Landscape::outVisits(int rep, int landNr) { if (sim.batchMode) { name = paramsSim->getDir(3) #if RS_RCPP - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) - + "_Land" + Int2Str(landNr) + "_Rep" + Int2Str(rep) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + + "_Land" + to_string(landNr) + "_Rep" + to_string(rep) #else - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) - + "_land" + Int2Str(landNr) + "_rep" + Int2Str(rep) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + + "_land" + to_string(landNr) + "_rep" + to_string(rep) #endif + "_Visits.txt"; } else { name = paramsSim->getDir(3) - + "Sim" + Int2Str(sim.simulation) - + "_land" + Int2Str(landNr) + "_rep" + Int2Str(rep) + + "Sim" + to_string(sim.simulation) + + "_land" + to_string(landNr) + "_rep" + to_string(rep) + "_Visits.txt"; } outvisits.open(name.c_str()); @@ -2655,11 +3318,51 @@ void Landscape::outVisits(int rep, int landNr) { outvisits << endl; } - outvisits.close(); outvisits.clear(); + outvisits.close(); + outvisits.clear(); } //--------------------------------------------------------------------------- +#ifdef UNIT_TESTS +// Tests only: shortcut setup utilities + +Landscape createLandscapeFromCells(vector cells, const landParams& lp, Species sp) { + // Set up landscape + Landscape ls; + ls.setLandParams(lp, true); + // Add cells + ls.setCellArray(); + for (auto c : cells) { + ls.addCellToLand(c); + } + ls.allocatePatches(&sp); + return ls; +} + +landParams createDefaultLandParams(const int& dim) { + + landParams ls_params; + ls_params.dimX = ls_params.dimY = dim; + ls_params.minX = ls_params.minY = 0; + ls_params.maxX = ls_params.maxY = ls_params.dimX - 1; + ls_params.resol = ls_params.spResol = 1; + ls_params.rasterType = 0; // habitat types + + ls_params.patchModel = false; + ls_params.spDist = false; + ls_params.generated = false; + ls_params.dynamic = false; + ls_params.landNum = 0; + ls_params.nHab = ls_params.nHabMax = 0; // irrelevant for habitat codes + return ls_params; +} + +void testLandscape() { + // test coordinate system... +} +#endif // UNIT_TESTS + //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- diff --git a/Landscape.h b/Landscape.h index 2a414cc..0c025ca 100644 --- a/Landscape.h +++ b/Landscape.h @@ -1,25 +1,25 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * --------------------------------------------------------------------------*/ - - + + + /*------------------------------------------------------------------------------ RangeShifter v2.0 Landscape @@ -58,14 +58,14 @@ to be intialised, which are specified by the user in FormSeeding. This option is available in the GUI version only. For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. + Bocedi G., Palmer S.C.F., Pe’er G., Heikkinen R.K., Matsinos Y.G., Watts K. and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. + eco-evolutionary dynamics and species’ responses to environmental changes. Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 Authors: Greta Bocedi & Steve Palmer, University of Aberdeen -Last updated: 2 December 2021 by Steve Palmer + Last updated: 28 July 2021 by Greta Bocedi ------------------------------------------------------------------------------*/ #ifndef LandscapeH @@ -88,9 +88,17 @@ using namespace std; #include #endif #include +#include #endif + //--------------------------------------------------------------------------- +// not sure whether it needs to be added here for the readDistribution() function to work + +struct landOrigin { + double minEast; double minNorth; +}; + // Initial species distribution @@ -98,9 +106,17 @@ class InitDist{ public: InitDist(Species*); ~InitDist(); +#if RS_RCPP int readDistribution( + Rcpp::NumericMatrix, + landOrigin, + int string // name of species distribution file ); +#endif + int readDistribution( + string // name of species distribution file + ); void setDistribution( int // no. of distribution cells to be initialised (0 for all cells) ); @@ -142,9 +158,10 @@ class InitDist{ //--------------------------------------------------------------------------- -struct landParams { +struct landParams { bool patchModel; bool spDist; bool generated; bool dynamic; + bool spatialdemog; int landNum; int resol; int spResol; int nHab; int nHabMax; int dimX,dimY,minX,minY,maxX,maxY; short rasterType; @@ -159,9 +176,11 @@ struct genLandParams { struct landPix { int pix; float gpix; }; +/* struct landOrigin { double minEast; double minNorth; }; + */ struct rasterHdr { bool ok; int errors,ncols,nrows,cellsize; @@ -179,7 +198,8 @@ struct patchData { Patch *pPatch; int patchNum,nCells; int x,y; }; struct landChange { - int chgnum,chgyear; string habfile,pchfile,costfile; + int chgNb, chgYear; + string pathHabFile, pathPatchFile, pathCostFile, pathSpatDemogFile; }; struct patchChange { int chgnum, x, y, oldpatch, newpatch; @@ -225,16 +245,11 @@ class Landscape{ int findHabCode(int); int getHabCode(int); void clearHabitats(void); - void addColour(rgb); - void changeColour(int,rgb); - rgb getColour(int); - int colourCount(void); // functions to handle patches and cells void setCellArray(void); - void addPatchNum(int); - void generatePatches(void); // create an artificial landscape + void generatePatches(); // create an artificial landscape void allocatePatches(Species*); // create patches for a cell-based landscape Patch* newPatch( int // patch sequential no. (id no. is set to equal sequential no.) @@ -254,6 +269,9 @@ class Landscape{ int, // y co-ordinate int // habitat class no. ); + void addCellToLand( + Cell* // cell to add to landscape + ); void addCellToPatch( Cell*, // pointer to Cell Patch* // pointer to Patch @@ -289,6 +307,8 @@ class Landscape{ Patch* findPatch( int // Patch id no. ); + set getPatchNbs() const; + set samplePatches(const string& samplingOption, int nbToSample, Species* pSpecies); int checkTotalCover(void); void resetPatchPopns(void); void updateCarryingCapacity( @@ -300,6 +320,10 @@ class Landscape{ int, // x co-ordinate int // y co-ordinate ); + bool checkDataCell( + int, // x co-ordinate + int // y co-ordinate + ); int patchCount(void); void updateHabitatIndices(void); void setEnvGradient( @@ -327,7 +351,19 @@ class Landscape{ short // change number ); void deleteLandChanges(void); +//#if RS_THREADSAFE #if RS_RCPP && !R_CMD + int readLandChange( + int, // change number + Rcpp::NumericMatrix,// habitat raster + Rcpp::NumericMatrix,// patch raster + Rcpp::NumericMatrix // cost raster +//#if SPATIALDEMOG + ,Rcpp::NumericVector// array of demographic scaling layers +//#endif + ); +//#else +// #if RS_RCPP && !R_CMD int readLandChange( int, // change file number bool, // change SMS costs? @@ -336,12 +372,16 @@ class Landscape{ wifstream&, // cost file stream int, // habnodata int, // pchnodata - int // costnodata + int, // costnodata + vector // vector of demographic scaling layers +// TODO: add spatial demography with normal file input ); #else + // TODO: add spatial demography for batch version int readLandChange( - int, // change file number - bool // change SMS costs? + int, // change file numbers + bool, // change SMS costs? + vector // vector of demographic scaling layers ); #endif void createPatchChgMatrix(void); @@ -358,6 +398,9 @@ class Landscape{ costChange getCostChange( int // cost change number ); +//#if SPATIALDEMOG + void updateDemoScalings(short); +//#endif // SPATIALDEMOG // functions to handle species distributions @@ -365,6 +408,19 @@ class Landscape{ Species*, // pointer to Species string // name of initial distribution file ); + + // function for new file input +#if RS_RCPP + int newDistribution( + Species*, // pointer to Species + //#if RS_THREADSAFE + Rcpp::NumericMatrix, + int + //#else + string // name of initial distribution file + ); +#endif + void setDistribution( Species*, // pointer to Species int // no. of distribution squares to initialise @@ -427,11 +483,11 @@ class Landscape{ int // sequential no. of settlement Patch ); void deleteConnectMatrix(void); - bool outConnectHeaders( // Write connectivity file headers - int // option - set to -999 to close the connectivity file - ); + bool outConnectFinishLandscape(); // Close connectivity file + bool outConnectStartLandscape(); // Open connectivity file and write header record #if RS_RCPP - void outPathsHeaders(int, int); + void outPathsFinishReplicate(); + void outPathsStartReplicate(int); #endif void outConnect( int, // replicate no. @@ -440,30 +496,28 @@ class Landscape{ // functions to handle input and output +#if RS_RCPP + int readLandscape( + int, // no. of seasonss + Rcpp::NumericMatrix,// habitat raster + Rcpp::NumericMatrix,// patch raster + Rcpp::NumericMatrix // cost raster + ,Rcpp::NumericVector // array of demographic scaling layers + ); +#endif int readLandscape( int, // fileNum == 0 for (first) habitat file and optional patch file // fileNum > 0 for subsequent habitat files under the %cover option string, // habitat file name string, // patch file name - string // cost file name (may be NULL) + string, // cost file name (may be NULL) + vector // demographic scaling layers (may be empty) ); void listPatches(void); int readCosts( string // costs file name ); - // the following four functions are implemented for the GUI version only - // in the batch version, they are defined, but empty - void setLandMap(void); - void drawLandscape( - int, // replicate no. - int, // landscape index number (always 0 if landscape is not dynamic) - int // landscape no. - ); - void drawGradient(void); // Draw environmental gradient map - void drawGlobalStoch( // Draw environmental stochasticity time-series - int // no. of years - ); - + int readDemographicScaling(vector ); void resetVisits(void); void outVisits(int,int); // save SMS path visits map to raster text file @@ -475,6 +529,9 @@ class Landscape{ bool continuous; // bool dynamic; // landscape changes during simulation bool habIndexed; // habitat codes have been converted to index numbers +//#if SPATIALDEMOG + bool spatialdemog; // are there spatially varying demographic rates? +//#endif // SPATIALDEMOG short rasterType; // 0 = habitat codes 1 = % cover 2 = quality 9 = artificial landscape int landNum; // landscape number int resol; // cell size (m) @@ -482,7 +539,7 @@ class Landscape{ int nHab; // no. of habitats int nHabMax; // max. no. of habitats (used for batch input only) int dimX,dimY; // dimensions - int minX,minY; // minimum available X and Y co-ordinates + int minX, minY; // minimum available X and Y co-ordinates, i.e. coordinates of the bottom-right corner int maxX,maxY; // maximum available X and Y co-ordinates float minPct,maxPct; // min and max percentage of habitat in a cell float propSuit; // proportion of suitable cells @@ -500,15 +557,9 @@ class Landscape{ // list of patches in the landscape - can be in any sequence std::vector patches; - // list of patch numbers in the landscape - std::vector patchnums; - // list of habitat codes std::vector habCodes; - // list of colours for habitat codes - std::vector colours; - // list of dynamic landscape changes std::vector landchanges; std::vector patchchanges; @@ -525,7 +576,7 @@ class Landscape{ int **connectMatrix; // global environmental stochasticity (epsilon) - float *epsGlobal; // pointer to time-series + float* epsGlobal; // pointer to time-series // patch and costs change matrices (temporary - used when reading dynamic landscape) // indexed by [descending y][x][period] @@ -545,13 +596,11 @@ extern paramInit *paramsInit; extern paramSim *paramsSim; extern RSrandom *pRandom; -#if RSDEBUG -extern ofstream DEBUGLOG; -extern void DebugGUI(string); +#ifdef UNIT_TESTS +landParams createDefaultLandParams(const int& dim); +void testLandscape(); #endif -extern void MemoLine(string); - #if RS_RCPP extern rasterdata landraster,patchraster,spdistraster,costsraster; extern void EOFerrorR(string); diff --git a/Main.cpp b/Main.cpp index 0d15cdb..cf631f8 100644 --- a/Main.cpp +++ b/Main.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -34,48 +34,41 @@ #include "RSrandom.h" #include "Utils.h" #include "Parameters.h" +#include "Population.h" #include "Landscape.h" #include "Species.h" #include "SubCommunity.h" +#include "Management.h" using namespace std; +#ifdef UNIT_TESTS +void testIndividual(); +void testNeutralStats(); +void testPopulation(); + void run_unit_tests() { cout << "******* Unit test output *******" << endl; testRSrandom(); + testLandscape(); testIndividual(); + testPopulation(); + testNeutralStats(); cout << endl << "************************" << endl; } +#endif // UNIT_TESTS // Global vars -string habmapname, patchmapname, distnmapname; -string costmapname, genfilename; string landFile; paramGrad* paramsGrad; paramStoch* paramsStoch; paramInit* paramsInit; paramSim* paramsSim; RSrandom* pRandom; -ofstream DEBUGLOG; -ofstream MUTNLOG; -vector hfnames; +Management* pManagement; // pointer to management routines Species* pSpecies; Community* pComm; -void DebugGUI(string msg) { - // nothing -} -void MemoLine(string msg) { - /// nothing -} -traitCanvas SetupTraitCanvas(void) { - traitCanvas tcanv; - for (int i = 0; i < NTRAITS; i++) { tcanv.pcanvas[i] = 0; } - return tcanv; -} -void Landscape::setLandMap(void) { } -void Landscape::drawLandscape(int rep, int yr, int landnum) { } -void Community::viewOccSuit(int year, double mn, double se) { } -void Community::draw(int rep, int yr, int gen, int landNum) { } +short nDSlayer=gMaxNbLayers; #if LINUX_CLUSTER || RS_RCPP int main(int argc, char* argv[]) @@ -83,10 +76,18 @@ int main(int argc, char* argv[]) int _tmain(int argc, _TCHAR* argv[]) #endif { -#ifdef NDEBUG - cout << "This code is only for running tests and not meant to run in release." << endl; +#ifndef UNIT_TESTS + cout << "This version is only for running unit tests." << endl; return 1; -# else +#else + + // Initialise globals + paramsGrad = new paramGrad; + paramsStoch = new paramStoch; + paramsInit = new paramInit; + paramsSim = new paramSim; + pRandom = new RSrandom; + assert(0.1 > 0.0); // assert does run correctly try { @@ -97,6 +98,7 @@ int _tmain(int argc, _TCHAR* argv[]) cerr << endl << "Error: " << e.what() << endl; } cout << "All tests have completed." << endl; + return 0; // if tests succeed, we are happy # endif // NDEBUG } diff --git a/Management.cpp b/Management.cpp new file mode 100644 index 0000000..9dd0504 --- /dev/null +++ b/Management.cpp @@ -0,0 +1,327 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Jette Wolff + --------------------------------------------------------------------------*/ + + +//--------------------------------------------------------------------------- + +#include "Management.h" + +//--------------------------------------------------------------------------- +/* + * Initialize management class + */ + +Management::Management(void) { + translocation = false; + catching_rate = 1.0; // Catching rate + non_dispersed = false; // do not consider non-dispersed individuals + std::vector translocation_years; // Number of years of translocation + std::map< int, std::vector > source; // Source patch or cell: should be a vector of arrays + std::map< int, std::vector > target; // Target patch or cell + std::map< int, std::vector > nb; // number of ttanslocated individuals + std::map< int, std::vector > min_age; // Minimum age of translocated individuals + std::map< int, std::vector > max_age; // Maximum age of translocated individuals + std::map< int, std::vector > stage; // Stage of translocated individuals + std::map< int, std::vector > sex; // Sex of translocated individuals + +} + +Management::~Management(void) { + translocation_years.clear(); + source.clear(); + target.clear(); + nb.clear(); + min_age.clear(); + max_age.clear(); + stage.clear(); +} + +managementParams Management::getManagementParams(void) { + managementParams m; + m.translocation = translocation; + return m; +} + +void Management::setManagementParams(const managementParams m){ + translocation = m.translocation; +}; + +translocationParams Management::getTranslocationParams(void) { + translocationParams t; + t.catching_rate = catching_rate; + t.translocation_years = translocation_years; + t.source = source; + t.target = target; + t.nb = nb; + t.min_age = min_age; + t.max_age = max_age; + t.stage = stage; + t.sex = sex; + return t; +} + +// not sure if this is a good way, so won't use it for now +void Management::setTranslocationParams(const translocationParams t){ + catching_rate = t.catching_rate; + translocation_years = t.translocation_years; + source = t.source; + target = t.target; + nb = t.nb; + min_age = t.min_age; + max_age = t.max_age; + stage = t.stage; + sex = t.sex; + +}; + +void Management::translocate(int yr + , Landscape* pLandscape + , Species* pSpecies + ){ +#if RS_RCPP + Rcpp::Rcout << "Start translocation events in year " << yr << endl; +#endif +#ifndef NDEBUG + cout << "Start translocation events in year " << yr << endl; +#endif + landParams ppLand = pLandscape->getLandParams(); + auto it = nb.find(yr); // the number of translocation events is determined by the number of elements of the maps at year yr + auto nb_it = nb.find(yr); + auto source_it = source.find(yr); + auto target_it = target.find(yr); + auto min_age_it = min_age.find(yr); + auto max_age_it = max_age.find(yr); + auto stage_it = stage.find(yr); + auto sex_it = sex.find(yr); + // iterate over the number of events + for (int e = 0; e < it->second.size(); e++) { +#if RS_RCPP + Rcpp::Rcout << "Translocation event " << e << " in year " << yr << endl; +#endif +#ifndef NDEBUG + cout << "Translocation event " << e << " in year " << yr << endl; +#endif + // find the source patch + Patch* s_patch; + Population* s_pPop; + if(ppLand.patchModel){ + if(pLandscape->existsPatch(source_it->second[e].x)){ +#if RS_RCPP + Rcpp::Rcout << "Source patch exist." << endl; +#endif +#ifndef NDEBUG + cout << "Source patch exist." << endl; +#endif + + s_patch = pLandscape->findPatch(source_it->second[e].x); + if (s_patch) { // if it is not a nullpointer + // test if population in patch is not zero + s_pPop = s_patch->getPopn(pSpecies); // returns the population of the species in that cell + if (s_pPop && s_pPop->getNbInds() > 0){ + } else { +#if RS_RCPP + Rcpp::Rcout << "Population does not exist in source patch or is 0! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Population does not exist in source patch or is 0! skipping translocation event." << endl; +#endif + return; + } + } else { +#if RS_RCPP + Rcpp::Rcout << "Source patch was found but NULL! skipping translocation event." << endl; // not sure if this ever happens +#endif +#ifndef NDEBUG + cout << "Source patch was found but NULL! skipping translocation event." << endl; // not sure if this ever happens +#endif + return; + } + // + } else{ +#if RS_RCPP + Rcpp::Rcout << "Source patch was not found in landscape! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Source patch was not found in landscape! skipping translocation event." << endl; +#endif + return; + } + } else{ + Cell* pCell; + pCell = pLandscape->findCell(source_it->second[e].x, source_it->second[e].y); + if (pCell != 0) { +#if RS_RCPP + Rcpp::Rcout << "Source cell was found" << endl; +#endif +#ifndef NDEBUG + cout << "Source cell was found" << endl; +#endif + Patch *s_ppatch = pCell->getPatch(); + if (s_ppatch) { + s_patch = s_ppatch; + // test if population in patch is not zero + s_pPop = s_patch->getPopn(pSpecies); // returns the population of the species in that cell + if (s_pPop && s_pPop->getNbInds() > 0){ + } else { +#if RS_RCPP + Rcpp::Rcout << "Population does not exist in source cell or is 0! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Population does not exist in source cell or is 0! skipping translocation event." << endl; +#endif + return; + } + } else { +#if RS_RCPP + Rcpp::Rcout << "Source cell does not exist! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Source cell does not exist! skipping translocation event." << endl; +#endif + + return; + } + } else { +#if RS_RCPP + Rcpp::Rcout << "Cell does not belong to landscape! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Cell does not belong to landscape! skipping translocation event." << endl; +#endif + return; + } + } + // find the target patch and check for existence + Patch* t_patch; + Population* t_pPop; + if(ppLand.patchModel){ + if(pLandscape->existsPatch(target_it->second[e].x)){ +#if RS_RCPP + Rcpp::Rcout << "Target patch exist." << endl; +#endif +#ifndef NDEBUG + cout << "Target patch exist." << endl; +#endif + t_patch = pLandscape->findPatch(target_it->second[e].x); + } else{ +#if RS_RCPP + Rcpp::Rcout << "Target patch was not found in landscape! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Target patch was not found in landscape! skipping translocation event." << endl; +#endif + return; + } + } else{ + Cell* pCell; + pCell = pLandscape->findCell(target_it->second[e].x, target_it->second[e].y); + if (pCell != 0) { +#if RS_RCPP + Rcpp::Rcout << "Target cell was found" << endl; +#endif +#ifndef NDEBUG + cout << "Target cell was found" << endl; +#endif + Patch *t_ppatch = pCell->getPatch(); + if (t_ppatch) { + t_patch = t_ppatch; + } else { +#if RS_RCPP + Rcpp::Rcout << "Target cell does not exist! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Target cell does not exist! skipping translocation event." << endl; +#endif + return; + } + } else { +#if RS_RCPP + Rcpp::Rcout << "Target cell does not belong to landscape! skipping translocation event." << endl; +#endif +#ifndef NDEBUG + cout << "Target cell does not belong to landscape! skipping translocation event." << endl; +#endif + return; + } + } + + // only if source and target cell/patch exist, we can translocate individuals: + // get individuals with the given characteristics in that population + int min_age = min_age_it->second[e]; + int max_age = max_age_it->second[e]; + int stage = stage_it->second[e]; + int sex = sex_it->second[e]; + int nb = nb_it->second[e]; + int nbSampledInds = 0; + // We made already sure by now that in s_pPop at least some individuals exist + nbSampledInds = s_pPop->sampleIndividuals(nb, min_age, max_age, stage, sex); // checking values was done when reading in the parameters + popStats s_stats = s_pPop->getStats(s_patch->getDemoScaling()); + Individual* catched_individual; + int translocated = 0; + // loop over all indsividuals, extract sampled individuals, try to catch individual + translocate them to new patch + for (int j = 0; j < s_stats.nInds; j++) { + // if there are individuals to catch + if(s_pPop->getSizeSampledInds()){ + // if this individual is matching one of the sampled individuals + catched_individual = s_pPop->catchIndividual(catching_rate, j); // catch individual in the source patch + if (catched_individual !=NULL) { // translocated individual - has already been removed from natal population + // Check if a population of this species already exists in target patch t_patch + t_pPop = t_patch->getPopn(pSpecies); + if (!t_pPop) { // translocated individual is the first in a previously uninhabited patch +#if RS_RCPP + Rcpp::Rcout << "Population does not exist in target patch. Creating new population." << endl; +#endif +#ifndef NDEBUG + cout << "Population does not exist in target patch. Creating new population." << endl; +#endif + // create a new population in the corresponding sub-community + SubCommunity* pSubComm = (SubCommunity*)t_patch->getSubComm(); + t_pPop = pSubComm->newPopn(pLandscape, pSpecies, t_patch, 0); + } + catched_individual->setStatus(10); // make sure individual is not dispersing after the translocation + t_pPop->recruit(catched_individual); // recruit individual to target population TODO: maybe use a specified function which also updates pCurrCell + pPrevCell to a random cell in target patch? + translocated ++; + // NOTE: + // the variables pCurrCell and pPrevCell are not updated! These are important for the dispersal process! + // currently, translocated individuals are not considered as potential emigrants, thus there is no problem in changing that + // however, if we want to consider dispersal events after translocation, we need to adapt that; but that would also mean, that we might loose the information + // about the natal patch of an individual? + simParams sim = paramsSim->getSim(); + if (sim.outConnect) { // increment connectivity totals + int newpatch = t_patch->getSeqNum(); + int prevpatch = s_patch->getSeqNum(); + pLandscape->incrConnectMatrix(prevpatch, newpatch); + } + + } + } + } +#if RS_RCPP + Rcpp::Rcout << "Successfully translocated " << translocated << " out of " << nb_it->second[e] << " individuals in translocation event " << e <<"." << endl; +#endif +#ifndef NDEBUG + cout << "Successfully translocated " << translocated << " out of " << nb_it->second[e] << " individuals in translocation event " << e <<"." << endl; +#endif + // remove pointers to sampled individuals + s_pPop->clean(); + } +}; diff --git a/Management.h b/Management.h new file mode 100644 index 0000000..6ef0f3c --- /dev/null +++ b/Management.h @@ -0,0 +1,136 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Jette Wolff + --------------------------------------------------------------------------*/ + + +/*------------------------------------------------------------------------------ + + RangeShifter v2.0 Parameters + + Implements the following classes: + + paramManagement - Management parameters + paramTranslocation - Translocation parameters + + + Last updated: 12 March 2024 by Jette Reeg + + ------------------------------------------------------------------------------*/ + + +#ifndef ManagementH +#define ManagementH + +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#if RS_RCPP +#include // for Rcpp::Rcout +#endif +#include "Parameters.h" +#include "Species.h" +#include "Cell.h" +#include "Landscape.h" + +#include "SubCommunity.h" +#include "Population.h" + + +#if RS_RCPP +typedef intptr_t intptr; +#else +typedef unsigned long long intptr; +#endif // RS_RCPP + + + +//--------------------------------------------------------------------------- + +/* + * Management settings + */ + +// Structure for management parameters +struct managementParams { + bool translocation; // Translocation +}; + +// Structure for translocation parameters +struct translocationParams { + double catching_rate; // Catching rate + std::vector translocation_years; // Number of years of translocation -> will be increased at the beginning of a simulation + std::map< int, std::vector > source; // Source patch or cell: should be a vector of arrays + std::map< int, std::vector > target; // Target patch or cell + std::map< int, std::vector > nb; // number of ttanslocated individuals + std::map< int, std::vector > min_age; // Minimum age of translocated individuals + std::map< int, std::vector > max_age; // Maximum age of translocated individuals + std::map< int, std::vector > stage; // Stage of translocated individuals + std::map< int, std::vector > sex; // Sex of translocated individuals +}; + + +//--------------------------------------------------------------------------- + +class Management{ +public: + Management(void); + ~Management(void); + void setManagementParams( // function to set management parameters + const managementParams // structure holding general management parameters + ); + managementParams getManagementParams(void); // get management parameters + void setTranslocationParams( // function to set translocation parameters + const translocationParams // structure holding translocation parameters + ); + translocationParams getTranslocationParams(void); + void translocate( // Translocation + int , // year of translocation + Landscape* , // pointer to the landscape + // Community*, // pointer to the community + Species* // pointer to the species + ); + + // + bool translocation; // Translocation + double catching_rate; // Catching rate + bool non_dispersed; // whether non-dispersed individuals should be translocated + std::vector translocation_years; // Number of years of translocation -> should be a dynamic vector + std::map< int, std::vector > source; // Source patch or cell: should be a vector of arrays + std::map< int, std::vector > target; // Target patch or cell + std::map< int, std::vector > nb; // number of ttanslocated individuals + std::map< int, std::vector > min_age; // Minimum age of translocated individuals + std::map< int, std::vector > max_age; // Maximum age of translocated individuals + std::map< int, std::vector > stage; // Stage of translocated individuals + std::map< int, std::vector > sex; // Sex of translocated individuals + +}; + +//--------------------------------------------------------------------------- + +extern paramSim *paramsSim; + +//--------------------------------------------------------------------------- +#endif diff --git a/Model.cpp b/Model.cpp index 3134f8d..0cb436b 100644 --- a/Model.cpp +++ b/Model.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -25,11 +25,11 @@ #include "Model.h" ofstream outPar; - +using namespace std::chrono; //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #if RS_RCPP && !R_CMD -Rcpp::List RunModel(Landscape* pLandscape, int seqsim) +Rcpp::List RunModel(Landscape* pLandscape, int seqsim, Rcpp::S4 ParMaster) //Rcpp::S4 ParMaster new for all variants #else int RunModel(Landscape* pLandscape, int seqsim) #endif @@ -40,21 +40,13 @@ int RunModel(Landscape* pLandscape, int seqsim) landParams ppLand = pLandscape->getLandParams(); envGradParams grad = paramsGrad->getGradient(); envStochParams env = paramsStoch->getStoch(); - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + transferRules trfr = pSpecies->getTransferRules(); + managementParams manage = pManagement->getManagementParams(); + translocationParams transloc = pManagement->getTranslocationParams(); initParams init = paramsInit->getInit(); simParams sim = paramsSim->getSim(); - simView v = paramsSim->getViews(); - -#if RSDEBUG - landPix p = pLandscape->getLandPix(); - DEBUGLOG << "RunModel(): reps=" << sim.reps - << " ppLand.nHab=" << ppLand.nHab - << " p.pix=" << p.pix - << endl; - DEBUGLOG << endl; -#endif if (!ppLand.generated) { if (!ppLand.patchModel) { // cell-based landscape @@ -62,11 +54,18 @@ int RunModel(Landscape* pLandscape, int seqsim) // NB this is an overhead here, but is necessary in case the identity of // suitable habitats has been changed from one simulation to another (GUI or batch) // substantial time savings may result during simulation in certain landscapes + // if using neutral markers, set up patches to sample from pLandscape->allocatePatches(pSpecies); } pComm = new Community(pLandscape); // set up community // set up a sub-community associated with each patch (incl. the matrix) pLandscape->updateCarryingCapacity(pSpecies, 0, 0); + + //if SPATIALDEMOG + if (ppLand.rasterType == 2 && ppLand.spatialdemog) + pLandscape->updateDemoScalings(0); // TODO -> is this needed independent of whether it is on or off? + // endif SPATIALDEMOG + patchData ppp; int npatches = pLandscape->patchCount(); for (int i = 0; i < npatches; i++) { @@ -81,6 +80,13 @@ int RunModel(Landscape* pLandscape, int seqsim) else { pLandscape->resetLandLimits(); } + + // Random patches are sampled once per landscape + if (sim.patchSamplingOption == "random") { + int nbToSample = pSpecies->getNbPatchesToSample(); + auto patchesToSample = pLandscape->samplePatches(sim.patchSamplingOption, nbToSample, pSpecies); + pSpecies->setSamplePatchList(patchesToSample); + } } #if RS_RCPP && !R_CMD @@ -89,79 +95,46 @@ int RunModel(Landscape* pLandscape, int seqsim) // Loop through replicates for (int rep = 0; rep < sim.reps; rep++) { -#if RSDEBUG - DEBUGLOG << endl << "RunModel(): starting simulation=" << sim.simulation << " rep=" << rep << endl; -#endif -#if RS_RCPP && !R_CMD - Rcpp::Rcout << endl << "starting replicate " << rep << endl; -#else -#if BATCH - cout << endl << "starting replicate " << rep << endl; -#endif -#endif - MemoLine(("Running replicate " + Int2Str(rep) + "...").c_str()); + cout << "Running replicate " << rep + 1 << " / " << sim.reps << endl; if (sim.saveVisits && !ppLand.generated) { pLandscape->resetVisits(); } + if (sim.fixReplicateSeed) { + pRandom->fixNewSeed(rep); + } patchChange patchchange; costChange costchange; int npatchchanges = pLandscape->numPatchChanges(); int ncostchanges = pLandscape->numCostChanges(); int ixpchchg = 0; int ixcostchg = 0; -#if RSDEBUG - DEBUGLOG << "RunModel(): npatchchanges=" << npatchchanges << " ncostchanges=" << ncostchanges << endl; -#endif if (ppLand.generated) { -#if RSDEBUG - DEBUGLOG << endl << "RunModel(): generating new landscape ..." << endl; -#endif // delete previous community (if any) // Note: this must be BEFORE the landscape is reset (as a sub-community accesses // its corresponding patch upon deletion) if (pComm != 0) delete pComm; // generate new cell-based landscape - MemoLine("...generating new landscape..."); pLandscape->resetLand(); -#if RSDEBUG - DEBUGLOG << "RunModel(): finished resetting landscape" << endl << endl; -#endif pLandscape->generatePatches(); - if (v.viewLand || sim.saveMaps) { - pLandscape->setLandMap(); - pLandscape->drawLandscape(rep, 0, ppLand.landNum); - } -#if RSDEBUG - DEBUGLOG << endl << "RunModel(): finished generating patches" << endl; -#endif pComm = new Community(pLandscape); // set up community // set up a sub-community associated with each patch (incl. the matrix) pLandscape->updateCarryingCapacity(pSpecies, 0, 0); patchData ppp; int npatches = pLandscape->patchCount(); -#if RSDEBUG - DEBUGLOG << "RunModel(): patch count is " << npatches << endl; -#endif for (int i = 0; i < npatches; i++) { ppp = pLandscape->getPatchData(i); -#if RSWIN64 -#if LINUX_CLUSTER - pComm->addSubComm(ppp.pPatch, ppp.patchNum); // SET UP ALL SUB-COMMUNITIES -#else - SubCommunity* pSubComm = pComm->addSubComm(ppp.pPatch, ppp.patchNum); // SET UP ALL SUB-COMMUNITIES -#endif -#else pComm->addSubComm(ppp.pPatch, ppp.patchNum); // SET UP ALL SUB-COMMUNITIES -#endif } - MemoLine("...completed..."); -#if RSDEBUG - DEBUGLOG << endl << "RunModel(): finished generating populations" << endl; -#endif + if (sim.patchSamplingOption == "random") { + // Then patches must be resampled for new landscape + int nbToSample = pSpecies->getNbPatchesToSample(); + auto patchesToSample = pLandscape->samplePatches(sim.patchSamplingOption, nbToSample, pSpecies); + pSpecies->setSamplePatchList(patchesToSample); + } } if (init.seedType == 0 && init.freeType < 2 && init.initFrzYr > 0) { // restrict available landscape to initialised region @@ -173,64 +146,78 @@ int RunModel(Landscape* pLandscape, int seqsim) } filesOK = true; +#if RS_RCPP + if(init.seedType==2 && init.indsFile=="NULL"){ // initialisation from InitInds list of dataframes + if(rep > 0){ + int error_init = 0; + Rcpp::S4 InitParamsR("InitialisationParams"); + InitParamsR = Rcpp::as(ParMaster.slot("init")); + Rcpp::List InitIndsList = Rcpp::as(InitParamsR.slot("InitIndsList")); + error_init = ReadInitIndsFileR(0, pLandscape, Rcpp::as(InitIndsList[rep])); + if(error_init>0) { + filesOK = false; + } + } + } +#endif if (rep == 0) { // open output files if (sim.outRange) { // open Range file - if (!pComm->outRangeHeaders(pSpecies, ppLand.landNum)) { - MemoLine("UNABLE TO OPEN RANGE FILE"); + if (!pComm->outRangeStartLandscape(pSpecies, ppLand.landNum)) { filesOK = false; } } if (sim.outOccup && sim.reps > 1) - if (!pComm->outOccupancyHeaders(0)) { - MemoLine("UNABLE TO OPEN OCCUPANCY FILE(S)"); + if (!pComm->outOccupancyStartLandscape()) { filesOK = false; } +#if RS_RCPP + if (sim.outPop && sim.CreatePopFile) { +#else if (sim.outPop) { +#endif // open Population file - if (!pComm->outPopHeaders(pSpecies, ppLand.landNum)) { - MemoLine("UNABLE TO OPEN POPULATION FILE"); + if (!pComm->outPopStartLandscape(pSpecies)) { filesOK = false; } } if (sim.outTraitsCells) - if (!pComm->outTraitsHeaders(pSpecies, ppLand.landNum)) { - MemoLine("UNABLE TO OPEN TRAITS FILE"); + if (!pComm->outTraitsStartLandscape(pSpecies, ppLand.landNum)) { filesOK = false; } if (sim.outTraitsRows) - if (!pComm->outTraitsRowsHeaders(pSpecies, ppLand.landNum)) { - MemoLine("UNABLE TO OPEN TRAITS ROWS FILE"); + if (!pComm->outTraitsRowsStartLandscape(pSpecies, ppLand.landNum)) { filesOK = false; } if (sim.outConnect && ppLand.patchModel) // open Connectivity file - if (!pLandscape->outConnectHeaders(0)) { - MemoLine("UNABLE TO OPEN CONNECTIVITY FILE"); + if (!pLandscape->outConnectStartLandscape()) { filesOK = false; } + if (sim.outputGlobalFst) { // open neutral genetics file + if (!pComm->openNeutralOutputFile(pSpecies, ppLand.landNum)) { + filesOK = false; + } + } } -#if RSDEBUG - DEBUGLOG << "RunModel(): completed opening output files" << endl; -#endif if (!filesOK) { -#if RSDEBUG - DEBUGLOG << "RunModel(): PROBLEM - closing output files" << endl; -#endif // close any files which may be open if (sim.outRange) { - pComm->outRangeHeaders(pSpecies, -999); + pComm->outRangeFinishLandscape(); } if (sim.outOccup && sim.reps > 1) - pComm->outOccupancyHeaders(-999); + pComm->outOccupancyFinishLandscape(); if (sim.outPop) { - pComm->outPopHeaders(pSpecies, -999); + pComm->outPopFinishLandscape(); } if (sim.outTraitsCells) - pComm->outTraitsHeaders(pSpecies, -999); + pComm->outTraitsFinishLandscape(); if (sim.outTraitsRows) - pComm->outTraitsRowsHeaders(pSpecies, -999); + pComm->outTraitsRowsFinishLandscape(); if (sim.outConnect && ppLand.patchModel) - pLandscape->outConnectHeaders(-999); + pLandscape->outConnectFinishLandscape(); + if (sim.outputGlobalFst) { + pComm->openNeutralOutputFile(pSpecies, -999); + } #if RS_RCPP && !R_CMD return Rcpp::List::create(Rcpp::Named("Errors") = 666); #else @@ -251,59 +238,50 @@ int RunModel(Landscape* pLandscape, int seqsim) pLandscape->createConnectMatrix(); // variables to control dynamic landscape - landChange landChg; landChg.chgnum = 0; landChg.chgyear = 999999; + landChange landChg; landChg.chgNb = 0; landChg.chgYear = 999999; if (!ppLand.generated && ppLand.dynamic) { landChg = pLandscape->getLandChange(0); // get first change year } // set up populations in the community pLandscape->updateCarryingCapacity(pSpecies, 0, 0); -#if RSDEBUG - DEBUGLOG << "RunModel(): completed updating carrying capacity" << endl; -#endif - // if (init.seedType != 2) { + + if (ppLand.rasterType == 2 && ppLand.spatialdemog) + pLandscape->updateDemoScalings(0); + +// if (init.seedType != 2) { pComm->initialise(pSpecies, -1); - // } bool updateland = false; int landIx = 0; // landscape change index -#if RSDEBUG - DEBUGLOG << "RunModel(): completed initialisation, rep=" << rep - << " pSpecies=" << pSpecies << endl; -#endif #if BATCH && RS_RCPP && !R_CMD Rcpp::Rcout << "RunModel(): completed initialisation " << endl; #endif // open a new individuals file for each replicate if (sim.outInds) - pComm->outInds(rep, 0, 0, ppLand.landNum); + pComm->outIndsStartReplicate(rep, ppLand.landNum); // open a new genetics file for each replicate - if (sim.outGenetics) { - pComm->outGenetics(rep, 0, 0, ppLand.landNum); - if (!dem.stageStruct && sim.outStartGenetic == 0) { - // write genetic data for initialised individuals of non-strucutred population - pComm->outGenetics(rep, 0, 0, -1); + if (sim.outputGenes) { + bool geneOutFileHasOpened = pComm->openOutGenesFile(pSpecies->isDiploid(), ppLand.landNum, rep); + if (!geneOutFileHasOpened) throw logic_error("Output gene value file could not be initialised."); } + + // open a new genetics file for each replicate for per locus and pairwise stats + if (sim.outputPerLocusFst) { + pComm->openPerLocusFstFile(pSpecies, pLandscape, ppLand.landNum, rep); + } + if (sim.outPairwiseFst) { + pComm->openPairwiseFstFile(pSpecies, pLandscape, ppLand.landNum, rep); } -#if RSDEBUG - // output initialised Individuals - if (sim.outInds) - pComm->outInds(rep, -1, -1, -1); -#endif #if RS_RCPP // open a new movement paths file for each replicate if (sim.outPaths) - pLandscape->outPathsHeaders(rep, 0); + pLandscape->outPathsStartReplicate(rep); #endif // years loop - MemoLine("...running..."); for (yr = 0; yr < sim.years; yr++) { -#if RSDEBUG - DEBUGLOG << endl << "RunModel(): starting simulation=" << sim.simulation - << " rep=" << rep << " yr=" << yr << endl; -#endif #if RS_RCPP && !R_CMD Rcpp::checkUserInterrupt(); #endif @@ -317,9 +295,9 @@ int RunModel(Landscape* pLandscape, int seqsim) || (yr < 3000001 && yr % 1000000 == 0) ) { #if RS_RCPP && !R_CMD - Rcpp::Rcout << "starting year " << yr << "..." << endl; + Rcpp::Rcout << "Starting year " << yr << "..." << endl; #else - cout << "starting year " << yr << endl; + cout << "Starting year " << yr << endl; #endif } if (init.seedType == 0 && init.freeType < 2) { @@ -336,13 +314,6 @@ int RunModel(Landscape* pLandscape, int seqsim) commStats s = pComm->getStats(); int minY = s.maxY - init.restrictRows; if (minY < 0) minY = 0; -#if RSDEBUG - DEBUGLOG << "RunModel(): restriction yr=" << yr - << " s.minY=" << s.minY << " s.maxY=" << s.maxY - << " init.restrictRows=" << init.restrictRows - << " minY=" << minY - << endl; -#endif pLandscape->setLandLimits(ppLand.minX, minY, ppLand.maxX, ppLand.maxY); updateCC = true; } @@ -350,11 +321,6 @@ int RunModel(Landscape* pLandscape, int seqsim) if (yr == init.finalFrzYr) { // apply final range restriction commStats s = pComm->getStats(); -#if RSDEBUG - DEBUGLOG << "RunModel(): final restriction yr=" << yr - << " s.minY=" << s.minY << " s.maxY=" << s.maxY - << endl; -#endif pLandscape->setLandLimits(ppLand.minX, s.minY, ppLand.maxX, s.maxY); updateCC = true; } @@ -374,22 +340,14 @@ int RunModel(Landscape* pLandscape, int seqsim) updateCC = true; } if (ppLand.dynamic) { -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " landChg.chgnum=" << landChg.chgnum - << " landChg.chgyear=" << landChg.chgyear - << " npatchchanges=" << npatchchanges << " ncostchanges=" << ncostchanges - << " ixpchchg=" << ixpchchg << " ixcostchg=" << ixcostchg - << endl; -#endif - if (yr == landChg.chgyear) { // apply landscape change - landIx = landChg.chgnum; + if (yr == landChg.chgYear) { // apply landscape change + landIx = landChg.chgNb; updateland = updateCC = true; if (ppLand.patchModel) { // apply any patch changes Patch* pPatch; Cell* pCell; patchchange = pLandscape->getPatchChange(ixpchchg++); while (patchchange.chgnum <= landIx && ixpchchg <= npatchchanges) { - // move cell from original patch to new patch pCell = pLandscape->findCell(patchchange.x, patchchange.y); if (patchchange.oldpatch != 0) { // not matrix @@ -403,17 +361,14 @@ int RunModel(Landscape* pLandscape, int seqsim) pPatch = pLandscape->findPatch(patchchange.newpatch); pPatch->addCell(pCell, patchchange.x, patchchange.y); } - pCell->setPatch((intptr)pPatch); + pCell->setPatch(pPatch); // get next patch change patchchange = pLandscape->getPatchChange(ixpchchg++); } ixpchchg--; pLandscape->resetPatches(); // reset patch limits } - if (landChg.costfile != "NULL") { // apply any SMS cost changes -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " landChg.costfile=" << landChg.costfile << endl; -#endif + if (landChg.pathCostFile != "NULL") { // apply any SMS cost changes Cell* pCell; costchange = pLandscape->getCostChange(ixcostchg++); while (costchange.chgnum <= landIx && ixcostchg <= ncostchanges) { @@ -430,7 +385,7 @@ int RunModel(Landscape* pLandscape, int seqsim) landChg = pLandscape->getLandChange(landIx); } else { - landChg.chgyear = 9999999; + landChg.chgYear = 9999999; } } } @@ -438,14 +393,17 @@ int RunModel(Landscape* pLandscape, int seqsim) if (updateCC) { pLandscape->updateCarryingCapacity(pSpecies, yr, landIx); - } + if (ppLand.rasterType == 2 && ppLand.spatialdemog) //ppLand.spatialdemog false by default + pLandscape->updateDemoScalings((short)landIx); + + } if (sim.outConnect && ppLand.patchModel) pLandscape->resetConnectMatrix(); if (ppLand.dynamic && updateland) { - if (trfr.moveModel && trfr.moveType == 1) { // SMS + if (trfr.usesMovtProc && trfr.moveType == 1) { // SMS if (!trfr.costMap) pLandscape->resetCosts(); // in case habitats have changed } // apply effects of landscape change to species present in changed patches @@ -472,36 +430,68 @@ int RunModel(Landscape* pLandscape, int seqsim) for (int gen = 0; gen < dem.repSeasons; gen++) // generation loop { -#if RSDEBUG - // TEMPORARY RANDOM STREAM CHECK - if (yr % 1 == 0) - { - DEBUGLOG << endl << "RunModel(): start of gen " << gen << " in year " << yr - << " for rep " << rep << " ("; - for (int i = 0; i < 5; i++) { - int rrrr = pRandom->IRandom(1000, 2000); - DEBUGLOG << " " << rrrr; - } - DEBUGLOG << " )" << endl; + // TODO move translocation before dispersal? + if (manage.translocation && std::find(transloc.translocation_years.begin(), transloc.translocation_years.end(), yr) != transloc.translocation_years.end()) { + pManagement->translocate(yr + , pLandscape + , pSpecies + ); } -#endif - if (v.viewPop || (sim.saveMaps && yr % sim.mapInt == 0)) { - if (updateland && gen == 0) { - pLandscape->drawLandscape(rep, landIx, ppLand.landNum); - } - pComm->draw(rep, yr, gen, ppLand.landNum); - } // Output and pop. visualisation before reproduction - if (v.viewPop || v.viewTraits || sim.outOccup - || sim.outTraitsCells || sim.outTraitsRows || sim.saveMaps) + if (sim.outOccup || sim.outTraitsCells || sim.outTraitsRows) PreReproductionOutput(pLandscape, pComm, rep, yr, gen); // for non-structured population, also produce range and population output now if (!dem.stageStruct && (sim.outRange || sim.outPop)) RangePopOutput(pComm, rep, yr, gen); #if RS_RCPP && !R_CMD - if (sim.ReturnPopRaster && sim.outPop && yr >= sim.outStartPop && yr % sim.outIntPop == 0) { - list_outPop.push_back(pComm->addYearToPopList(rep, yr), "rep" + std::to_string(rep) + "_year" + std::to_string(yr)); + if ((sim.ReturnPopMatrix || sim.ReturnPopDataFrame) && sim.outPop && yr >= sim.outStartPop && yr % sim.outIntPop == 0) { + + // if ReturnPopMatrix + if(sim.ReturnPopMatrix) { + // total abundance + list_outPop.push_back( + pComm->addYearToPopList(rep, yr, PopOutType::NInd, -1), + "rep" + std::to_string(rep) + "_year" + std::to_string(yr) + "_NInd" + ); + + // also output single stages + if(sim.ReturnStages.length() > 1){ + bool ReturnStage; + for(int i = 1; i < sim.ReturnStages.length(); i++) { + ReturnStage = sim.ReturnStages[i] == 1; + if(ReturnStage) { + list_outPop.push_back( + pComm->addYearToPopList(rep, yr, PopOutType::Stage, i), + "rep" + std::to_string(rep) + + "_year" + std::to_string(yr) + + "_NInd_stage" + std::to_string(i) + ); + } + } + // + // + ReturnStage = sim.ReturnStages[0] == 1; + if (ReturnStage) { + //Rcpp::Rcout << "Return Juveniles" << endl; + list_outPop.push_back( + pComm->addYearToPopList(rep, yr, PopOutType::Juvs, -1), + "rep" + std::to_string(rep) + "_year" + std::to_string(yr) + "_NJuv" + ); + } + } + } + + if(sim.ReturnPopDataFrame){ + // in contrast to ReturnMatrix, this function produces a list of data frames, one per rep and year, holding all (stage-specific) abundances for all patches, + // instead of a single matrix for each population size metric (total abundance, stage-specific abundance, juvenile abundance) and each year and replicate + list_outPop.push_back( + pComm->addYearToPopListPatchBased(rep, yr, sim.ReturnStages), + "rep" + std::to_string(rep) + "_year" + std::to_string(yr) + ); + } + + // list_outPop.push_back(pComm->addYearToPopList(rep, yr), "rep" + std::to_string(rep) + "_year" + std::to_string(yr)); } #endif // apply local extinction for generation 0 only @@ -517,7 +507,7 @@ int RunModel(Landscape* pLandscape, int seqsim) if (dem.stageStruct) { if (sstruct.survival == 0) { // at reproduction - pComm->survival(0, 2, 1); // survival of all non-juvenile stages + pComm->survival0(2, 1); // survival of all non-juvenile stages } } @@ -525,70 +515,93 @@ int RunModel(Landscape* pLandscape, int seqsim) if (dem.stageStruct && (sim.outRange || sim.outPop)) RangePopOutput(pComm, rep, yr, gen); -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " gen=" << gen << " completed reproduction" << endl; -#endif - // Dispersal - pComm->emigration(); -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " gen=" << gen << " completed emigration" << endl; -#endif #if RS_RCPP pComm->dispersal(landIx, yr); #else pComm->dispersal(landIx); #endif // RS_RCPP -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " gen=" << gen << " completed dispersal" << endl; -#endif // survival part 0 if (dem.stageStruct) { if (sstruct.survival == 0) { // at reproduction - pComm->survival(0, 0, 1); // survival of juveniles only + pComm->survival0(0, 1); // survival of juveniles only } if (sstruct.survival == 1) { // between reproduction events - pComm->survival(0, 1, 1); // survival of all stages + pComm->survival0(1, 1); // survival of all stages } if (sstruct.survival == 2) { // annually - pComm->survival(0, 1, 0); // development only of all stages + pComm->survival0(1, 0); // development only of all stages } } else { // non-structured population - pComm->survival(0, 1, 1); + pComm->survival0(1, 1); } -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " gen=" << gen << " completed survival part 0" << endl; -#endif - // output Individuals if (sim.outInds && yr >= sim.outStartInd && yr % sim.outIntInd == 0) - pComm->outInds(rep, yr, gen, -1); - // output Genetics - if (sim.outGenetics && yr >= sim.outStartGenetic && yr % sim.outIntGenetic == 0) - pComm->outGenetics(rep, yr, gen, -1); + pComm->outIndividuals(rep, yr, gen); + + bool doGenes = + sim.outputGenes && + yr >= sim.outputGenesStart && + sim.outputGenesInterval > 0 && + yr % sim.outputGenesInterval == 0; + + bool doGlobalFst = + sim.outputGlobalFst && + yr >= sim.outputGlobalFstStart && + sim.outputGlobalFstInterval > 0 && + yr % sim.outputGlobalFstInterval == 0; + + bool doPairwiseFst = + sim.outPairwiseFst && + yr >= sim.outputPairwiseFstStart && + sim.outputPairwiseFstInterval > 0 && + yr % sim.outputPairwiseFstInterval == 0; + + if (doGenes || doGlobalFst || doPairwiseFst) { + + if (sim.patchSamplingOption != "list" && + sim.patchSamplingOption != "random") { + + int nbToSample = pSpecies->getNbPatchesToSample(); + auto patchesToSample = + pLandscape->samplePatches(sim.patchSamplingOption, nbToSample, pSpecies); + pSpecies->setSamplePatchList(patchesToSample); + } - // survival part 1 - if (dem.stageStruct) { - pComm->survival(1, 0, 1); - } - else { // non-structured population - pComm->survival(1, 0, 1); + pComm->sampleIndividuals(pSpecies); + + if (doGenes) { + pComm->outputGeneValues(yr, gen, pSpecies); + } + + if (doGlobalFst || doPairwiseFst) { + pComm->calculateNeutralGenetics( + pSpecies, rep, yr, gen, + doPairwiseFst, sim.outputPairwiseFstStart, sim.outputPairwiseFstInterval, + doGlobalFst, sim.outputGlobalFstStart, sim.outputGlobalFstInterval, + sim.outputPerLocusFst); + } } -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " gen=" << gen << " completed survival part 1" << endl; -#endif + + + + + + // Resolve survival and devlpt + pComm->survival1(); } // end of the generation loop -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " completed generation loop" << endl; -#endif totalInds = pComm->totalInds(); - if (totalInds <= 0) { yr++; break; } + if (totalInds <= 0) { + cout << "All populations went extinct." << endl; + yr++; + break; + } // Connectivity Matrix if (sim.outConnect && ppLand.patchModel @@ -596,45 +609,31 @@ int RunModel(Landscape* pLandscape, int seqsim) pLandscape->outConnect(rep, yr); if (dem.stageStruct && sstruct.survival == 2) { // annual survival - all stages - pComm->survival(0, 1, 2); - pComm->survival(1, 0, 1); -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " completed annual survival" << endl; -#endif + pComm->survival0(1, 2); + pComm->survival1(); } if (dem.stageStruct) { pComm->ageIncrement(); // increment age of all individuals if (sim.outInds && yr >= sim.outStartInd && yr % sim.outIntInd == 0) - pComm->outInds(rep, yr, -1, -1); // list any individuals dying having reached maximum age - pComm->survival(1, 0, 1); // delete any such individuals -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " completed Age_increment and final survival" << endl; -#endif + pComm->outIndividuals(rep, yr, -1); // list any individuals dying having reached maximum age + pComm->survival1(); // delete any such individuals totalInds = pComm->totalInds(); - if (totalInds <= 0) { yr++; break; } + if (totalInds <= 0) { + cout << "All populations went extinct." << endl; + yr++; + break; + } } } // end of the years loop - // Final output and popn. visualisation -#if BATCH - if (sim.saveMaps && yr % sim.mapInt == 0) { - if (updateland) { - pLandscape->drawLandscape(rep, landIx, ppLand.landNum); - } - pComm->draw(rep, yr, 0, ppLand.landNum); - } -#endif + // Final output // produce final summary output - if (v.viewPop || v.viewTraits || sim.outOccup - || sim.outTraitsCells || sim.outTraitsRows || sim.saveMaps) + if (sim.outOccup || sim.outTraitsCells || sim.outTraitsRows) PreReproductionOutput(pLandscape, pComm, rep, yr, 0); if (sim.outRange || sim.outPop) RangePopOutput(pComm, rep, yr, 0); -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " completed final summary output" << endl; -#endif pComm->resetPopns(); @@ -642,12 +641,6 @@ int RunModel(Landscape* pLandscape, int seqsim) if (grad.gradient) paramsGrad->resetOptY(); pLandscape->resetLandLimits(); -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " landIx=" << "reset" - << " npatchchanges=" << npatchchanges << " ncostchanges=" << ncostchanges - << " ixpchchg=" << ixpchchg << " ixcostchg=" << ixcostchg - << endl; -#endif if (ppLand.patchModel && ppLand.dynamic && ixpchchg > 0) { // apply any patch changes to reset landscape to original configuration // (provided that at least one has already occurred) @@ -656,7 +649,6 @@ int RunModel(Landscape* pLandscape, int seqsim) Cell* pCell; patchchange = pLandscape->getPatchChange(ixpchchg++); while (patchchange.chgnum <= 666666 && ixpchchg <= npatchchanges) { - // move cell from original patch to new patch pCell = pLandscape->findCell(patchchange.x, patchchange.y); if (patchchange.oldpatch != 0) { // not matrix @@ -670,7 +662,7 @@ int RunModel(Landscape* pLandscape, int seqsim) pPatch = pLandscape->findPatch(patchchange.newpatch); pPatch->addCell(pCell, patchchange.x, patchchange.y); } - pCell->setPatch((intptr)pPatch); + pCell->setPatch(pPatch); // get next patch change patchchange = pLandscape->getPatchChange(ixpchchg++); } @@ -678,15 +670,14 @@ int RunModel(Landscape* pLandscape, int seqsim) pLandscape->resetPatches(); } if (ppLand.dynamic) { - trfrRules trfr = pSpecies->getTrfr(); - if (trfr.moveModel && trfr.moveType == 1) { // SMS + transferRules trfr = pSpecies->getTransferRules(); + if (trfr.usesMovtProc && trfr.moveType == 1) { // SMS if (ixcostchg > 0) { // apply any cost changes to reset landscape to original configuration // (provided that at least one has already occurred) Cell* pCell; costchange = pLandscape->getCostChange(ixcostchg++); while (costchange.chgnum <= 666666 && ixcostchg <= ncostchanges) { - pCell = pLandscape->findCell(costchange.x, costchange.y); if (pCell != 0) { pCell->setCost(costchange.newcost); @@ -699,18 +690,23 @@ int RunModel(Landscape* pLandscape, int seqsim) if (!trfr.costMap) pLandscape->resetCosts(); // in case habitats have changed } } -#if RSDEBUG - DEBUGLOG << "RunModel(): yr=" << yr << " completed reset" - << endl; -#endif if (sim.outConnect && ppLand.patchModel) pLandscape->resetConnectMatrix(); // set connectivity matrix to zeroes if (sim.outInds) // close Individuals output file - pComm->outInds(rep, 0, 0, -999); - if (sim.outGenetics) // close Genetics output file - pComm->outGenetics(rep, 0, 0, -999); + pComm->outIndsFinishReplicate(); + + if (sim.outputGenes) { // close genetic values output file + pComm->openOutGenesFile(false, -999, rep); + } + + //if (sim.outputGlobalFst) //close per locus file + // pComm->openNeutralOutputFile(pSpecies, -999); + if (sim.outputPerLocusFst) //close per locus file + pComm->openPerLocusFstFile(pSpecies, pLandscape, -999, rep); + if (sim.outPairwiseFst) //close per locus file + pComm->openPairwiseFstFile(pSpecies, pLandscape, -999, rep); if (sim.saveVisits) { pLandscape->outVisits(rep, ppLand.landNum); @@ -719,47 +715,52 @@ int RunModel(Landscape* pLandscape, int seqsim) #if RS_RCPP if (sim.outPaths) - pLandscape->outPathsHeaders(rep, -999); -#endif -#if RSDEBUG - DEBUGLOG << endl << "RunModel(): finished rep=" << rep << endl; + pLandscape->outPathsFinishReplicate(); #endif } // end of the replicates loop if (sim.outConnect && ppLand.patchModel) { pLandscape->deleteConnectMatrix(); - pLandscape->outConnectHeaders(-999); // close Connectivity Matrix file + pLandscape->outConnectFinishLandscape(); // close Connectivity Matrix file } // Occupancy outputs if (sim.outOccup && sim.reps > 1) { - MemoLine("Writing final occupancy output..."); pComm->outOccupancy(); - pComm->outOccSuit(v.viewGraph); + pComm->outOccSuit(); pComm->deleteOccupancy((sim.years / sim.outIntOcc) + 1); - pComm->outOccupancyHeaders(-999); - MemoLine("...finished"); + pComm->outOccupancyFinishLandscape(); } if (sim.outRange) { - pComm->outRangeHeaders(pSpecies, -999); // close Range file + pComm->outRangeFinishLandscape(); // close Range file } +#if RS_RCPP + if (sim.outPop && sim.CreatePopFile) { +#else if (sim.outPop) { - pComm->outPopHeaders(pSpecies, -999); // close Population file +#endif + pComm->outPopFinishLandscape(); // close Population file } if (sim.outTraitsCells) - pComm->outTraitsHeaders(pSpecies, -999); // close Traits file + pComm->outTraitsFinishLandscape(); // close Traits file if (sim.outTraitsRows) - pComm->outTraitsRowsHeaders(pSpecies, -999); // close Traits rows file + pComm->outTraitsRowsFinishLandscape(); // close Traits rows file // close Individuals & Genetics output files if open // they can still be open if the simulation was stopped by the user - if (sim.outInds) pComm->outInds(0, 0, 0, -999); - if (sim.outGenetics) pComm->outGenetics(0, 0, 0, -999); + if (sim.outInds) pComm->outIndsFinishReplicate(); + if (sim.outputGenes) pComm->openOutGenesFile(0, -999, 0); + if (sim.outputGlobalFst) { + pComm->openNeutralOutputFile(pSpecies, -999); + } + if (sim.outputPerLocusFst) { + pComm->openPerLocusFstFile(pSpecies, pLandscape, -999, 0); + } + if (sim.outPairwiseFst) pComm->openPairwiseFstFile(pSpecies, pLandscape, -999, 0); - MemoLine("Deleting community..."); - delete pComm; pComm = 0; - MemoLine("...finished"); + delete pComm; + pComm = 0; #if RS_RCPP && !R_CMD return list_outPop; @@ -780,61 +781,46 @@ bool is_directory(const char* pathname) { #endif //--------------------------------------------------------------------------- -bool CheckDirectory(void) +bool CheckDirectory(const string& pathToProjDir) { bool errorfolder = false; string subfolder; - subfolder = paramsSim->getDir(0) + "Inputs"; + subfolder = pathToProjDir + "Inputs"; const char* inputs = subfolder.c_str(); if (!is_directory(inputs)) errorfolder = true; - subfolder = paramsSim->getDir(0) + "Outputs"; + subfolder = pathToProjDir + "Outputs"; const char* outputs = subfolder.c_str(); if (!is_directory(outputs)) errorfolder = true; - subfolder = paramsSim->getDir(0) + "Output_Maps"; + subfolder = pathToProjDir + "Output_Maps"; const char* outputmaps = subfolder.c_str(); if (!is_directory(outputmaps)) errorfolder = true; - return errorfolder; + if (errorfolder) { + cout << endl << "***** Invalid working directory: " << pathToProjDir + << endl << endl; + cout << "***** Working directory must contain Inputs, Outputs and Output_Maps folders" + << endl << endl; + cout << "*****" << endl; + cout << "***** Simulation ABORTED" << endl; + cout << "*****" << endl; + return false; +} + else return true; } //--------------------------------------------------------------------------- //For outputs and population visualisations pre-reproduction void PreReproductionOutput(Landscape* pLand, Community* pComm, int rep, int yr, int gen) { -#if RSDEBUG - landParams ppLand = pLand->getLandParams(); -#endif simParams sim = paramsSim->getSim(); - simView v = paramsSim->getViews(); - -#if RSDEBUG - DEBUGLOG << "PreReproductionOutput(): 11111 rep=" << rep << " yr=" << yr << " gen=" << gen - << " landNum=" << ppLand.landNum << " maxX=" << ppLand.maxX << " maxY=" << ppLand.maxY - << endl; - DEBUGLOG << "PreReproductionOutput(): 11112 outRange=" << sim.outRange - << " outIntRange=" << sim.outIntRange - << " outPop=" << sim.outPop << " outIntPop=" << sim.outIntPop - << endl; -#endif - - traitCanvas tcanv; - for (int i = 0; i < NTRAITS; i++) { - tcanv.pcanvas[i] = 0; - } // trait outputs and visualisation - - if (v.viewTraits) { - tcanv = SetupTraitCanvas(); - } - - if (v.viewTraits - || ((sim.outTraitsCells && yr >= sim.outStartTraitCell && yr % sim.outIntTraitCell == 0) || - (sim.outTraitsRows && yr >= sim.outStartTraitRow && yr % sim.outIntTraitRow == 0))) + if ((sim.outTraitsCells && yr >= sim.outStartTraitCell && yr % sim.outIntTraitCell == 0) + || (sim.outTraitsRows && yr >= sim.outStartTraitRow && yr % sim.outIntTraitRow == 0)) { - pComm->outTraits(tcanv, pSpecies, rep, yr, gen); + pComm->outTraits(pSpecies, rep, yr, gen); } if (sim.outOccup && yr % sim.outIntOcc == 0 && gen == 0) pComm->updateOccupancy(yr / sim.outIntOcc, rep); @@ -848,7 +834,11 @@ void RangePopOutput(Community* pComm, int rep, int yr, int gen) if (sim.outRange && (yr % sim.outIntRange == 0 || pComm->totalInds() <= 0)) pComm->outRange(pSpecies, rep, yr, gen); +#if RS_RCPP +if (sim.outPop && sim.CreatePopFile && yr >= sim.outStartPop && yr%sim.outIntPop == 0) +#else if (sim.outPop && yr >= sim.outStartPop && yr % sim.outIntPop == 0) +#endif pComm->outPop(rep, yr, gen); } @@ -864,10 +854,10 @@ void OutParameters(Landscape* pLandscape) genLandParams ppGenLand = pLandscape->getGenLandParams(); envGradParams grad = paramsGrad->getGradient(); envStochParams env = paramsStoch->getStoch(); - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); settleRules srules; settleSteps ssteps; @@ -877,22 +867,15 @@ void OutParameters(Landscape* pLandscape) string name; if (sim.batchMode) name = paramsSim->getDir(2) - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) - + "_Land" + Int2Str(ppLand.landNum) + "_Parameters.txt"; + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + + "_Land" + to_string(ppLand.landNum) + "_Parameters.txt"; else - name = paramsSim->getDir(2) + "Sim" + Int2Str(sim.simulation) + "_Parameters.txt"; + name = paramsSim->getDir(2) + "Sim" + to_string(sim.simulation) + "_Parameters.txt"; outPar.open(name.c_str()); - outPar << "RangeShifter 2.0 "; + outPar << "RangeShifter 3.0 "; -#if !RS_RCPP -#if RSWIN64 - outPar << " - 64 bit implementation"; -#else - outPar << " - 32 bit implementation"; -#endif -#endif outPar << endl; outPar << "================ "; @@ -901,10 +884,9 @@ void OutParameters(Landscape* pLandscape) outPar << endl << endl; outPar << "BATCH MODE \t"; - if (sim.batchMode) outPar << "yes" << endl; else outPar << "no" << endl; -#if RS_RCPP - outPar << "SEED \t" << RS_random_seed << endl; -#endif + if (sim.batchMode) outPar << "yes" << endl; + else outPar << "no" << endl; + outPar << "SEED \t" << pRandom->getSeed() << endl; outPar << "REPLICATES \t" << sim.reps << endl; outPar << "YEARS \t" << sim.years << endl; outPar << "REPRODUCTIVE SEASONS / YEAR\t" << dem.repSeasons << endl; @@ -960,17 +942,6 @@ void OutParameters(Landscape* pLandscape) } #else if (sim.batchMode) outPar << " (see batch file) " << landFile << endl; - else { - outPar << habmapname << endl; - if (ppLand.rasterType == 1) { // habitat % cover - list additional layers - for (int i = 0; i < ppLand.nHab - 1; i++) { - outPar << " " << hfnames[i] << endl; - } - } - if (ppLand.patchModel) { - outPar << "PATCH FILE: " << patchmapname << endl; - } - } #endif outPar << "No. HABITATS:\t" << ppLand.nHab << endl; } @@ -984,17 +955,17 @@ void OutParameters(Landscape* pLandscape) int nchanges = pLandscape->numLandChanges(); for (int i = 0; i < nchanges; i++) { chg = pLandscape->getLandChange(i); - outPar << "Change no. " << chg.chgnum << " in year " << chg.chgyear << endl; - outPar << "Landscape: " << chg.habfile << endl; + outPar << "Change no. " << chg.chgNb << " in year " << chg.chgYear << endl; + outPar << "Landscape: " << chg.pathHabFile << endl; if (ppLand.patchModel) { - outPar << "Patches : " << chg.pchfile << endl; + outPar << "Patches : " << chg.pathPatchFile << endl; } - if (chg.costfile != "none" && chg.costfile != "NULL") { - outPar << "Costs : " << chg.costfile << endl; + if (chg.pathCostFile != "none" && chg.pathCostFile != "NULL") { + outPar << "Costs : " << chg.pathCostFile << endl; } + } } - outPar << endl << "SPECIES DISTRIBUTION LOADED: \t"; if (ppLand.spDist) { @@ -1003,9 +974,6 @@ void OutParameters(Landscape* pLandscape) outPar << "FILE NAME: "; #if !RS_RCPP if (sim.batchMode) outPar << " (see batch file) " << landFile << endl; - else { - outPar << distnmapname << endl; - } #else outPar << name_sp_dist << endl; #endif @@ -1182,7 +1150,7 @@ void OutParameters(Landscape* pLandscape) } int mSize; // index for weights matrices - if (dem.repType == 2) mSize = sstruct.nStages * NSEXES; + if (dem.repType == 2) mSize = sstruct.nStages * gMaxNbSexes; else mSize = sstruct.nStages; outPar << "DENSITY-DEPENDENCE IN FECUNDITY:\t"; @@ -1192,8 +1160,8 @@ void OutParameters(Landscape* pLandscape) outPar << "STAGE'S WEIGHTS:" << endl; for (int i = 0; i < mSize; i++) { if (dem.repType == 2) { - outPar << "stage " << i / NSEXES << " "; - if (i % NSEXES == 0) outPar << "males : \t"; + outPar << "stage " << i / gMaxNbSexes << " "; + if (i % gMaxNbSexes == 0) outPar << "males : \t"; else outPar << "females: \t"; } else outPar << "stage " << i << ": \t"; @@ -1214,8 +1182,8 @@ void OutParameters(Landscape* pLandscape) outPar << "STAGE'S WEIGHTS:" << endl; for (int i = 0; i < mSize; i++) { if (dem.repType == 2) { - outPar << "stage " << i / NSEXES << " "; - if (i % NSEXES == 0) outPar << "males : \t"; + outPar << "stage " << i / gMaxNbSexes << " "; + if (i % gMaxNbSexes == 0) outPar << "males : \t"; else outPar << "females: \t"; } else outPar << "stage " << i << ": \t"; @@ -1234,8 +1202,8 @@ void OutParameters(Landscape* pLandscape) outPar << "STAGE'S WEIGHTS:" << endl; for (int i = 0; i < mSize; i++) { if (dem.repType == 2) { - outPar << "stage " << i / NSEXES << " "; - if (i % NSEXES == 0) outPar << "males : \t"; + outPar << "stage " << i / gMaxNbSexes << " "; + if (i % gMaxNbSexes == 0) outPar << "males : \t"; else outPar << "females: \t"; } else outPar << "stage " << i << ": \t"; @@ -1246,6 +1214,49 @@ void OutParameters(Landscape* pLandscape) else outPar << "not stage-dependent" << endl; } else outPar << "no" << endl; + + if (ppLand.spatialdemog){ + outPar << "SPATIALLY VARYING DEMOGRAPHY:\t in" << endl; + // file names for the spatial layers + + if(pSpecies->getFecSpatial()){ + outPar << "FECUNDITY" << endl; + outPar << "LAYERS:" << endl; + for (int i = 0; i < sstruct.nStages; i++) { + if (dem.repType == 2){ + outPar << "stage: " << i << "females: \t" << pSpecies->getFecLayer(i,0) << "\tmales: \t" << pSpecies->getFecLayer(i,1) << endl; + } else{ + outPar << "stage: " << i << pSpecies->getFecLayer(i,0) << endl; + } + } + } + + if(pSpecies->getDevSpatial()){ + outPar << "DEVELOPMENT" << endl; + outPar << "LAYERS:" << endl; + for (int i = 0; i < sstruct.nStages; i++) { + if (dem.repType == 2){ + outPar << "stage: " << i << "females: \t" << pSpecies->getDevLayer(i,0) << "\tmales: \t" << pSpecies->getDevLayer(i,1) << endl; + } else{ + outPar << "stage: " << i << pSpecies->getDevLayer(i,0) << endl; + } + } + } + if(pSpecies->getSurvSpatial()){ + outPar << "SURVIVAL" << endl; + outPar << "LAYERS:" << endl; + for (int i = 0; i < sstruct.nStages; i++) { + if (dem.repType == 2){ + outPar << "stage: " << i << "females: \t" << pSpecies->getSurvLayer(i,0) << "\tmales: \t" << pSpecies->getSurvLayer(i,1) << endl; + } else{ + outPar << "stage: " << i << pSpecies->getSurvLayer(i,0) << endl; + } + } + } + } + else { + outPar << "SPATIALLY VARYING DEMOGRAPHY:\t no" << endl; + } } // end of if (dem.stageStruct) else { // not stage-strutured outPar << "no" << endl; @@ -1275,9 +1286,7 @@ void OutParameters(Landscape* pLandscape) else outPar << "K "; outPar << k << endl; } - emigTraits ep0, ep1; - emigParams eparams0, eparams1; string sexdept = "SEX-DEPENDENT: "; string stgdept = "STAGE-DEPENDENT: "; string indvar = "INDIVIDUAL VARIABILITY: "; @@ -1286,7 +1295,6 @@ void OutParameters(Landscape* pLandscape) outPar << endl << "DISPERSAL - EMIGRATION:\t"; if (emig.densDep) { outPar << "density-dependent" << endl; - if (emig.sexDep) { outPar << sexdept << "yes" << endl; if (emig.stgDep) { @@ -1294,8 +1302,8 @@ void OutParameters(Landscape* pLandscape) outPar << indvar << "no" << endl; for (int i = 0; i < sstruct.nStages; i++) { outPar << "stage " << i << ":" << endl; - ep0 = pSpecies->getEmigTraits(i, 0); - ep1 = pSpecies->getEmigTraits(i, 1); + ep0 = pSpecies->getSpEmigTraits(i, 0); + ep1 = pSpecies->getSpEmigTraits(i, 1); outPar << "D0: females " << ep0.d0 << " males " << ep1.d0 << endl; outPar << "alpha: females " << ep0.alpha << " males " << ep1.alpha << endl; outPar << "beta: females " << ep0.beta << " males " << ep1.beta << endl; @@ -1303,79 +1311,37 @@ void OutParameters(Landscape* pLandscape) } else { // !emig.stgDep outPar << stgdept << "no" << endl; - outPar << indvar; - if (emig.indVar) { - eparams0 = pSpecies->getEmigParams(0, 0); - eparams1 = pSpecies->getEmigParams(0, 1); - outPar << "yes" << endl; - if (dem.stageStruct) { - outPar << emigstage << emig.emigStage << endl; - } - outPar << "D0 females: mean " << eparams0.d0Mean << " s.d. " << eparams0.d0SD - << " scaling factor " << eparams0.d0Scale << endl; - outPar << "D0 males: mean " << eparams1.d0Mean << " s.d. " << eparams1.d0SD - << " scaling factor " << eparams1.d0Scale << endl; - outPar << "Alpha females: mean " << eparams0.alphaMean << " s.d. " << eparams0.alphaSD - << " scaling factor " << eparams0.alphaScale << endl; - outPar << "Alpha males: mean " << eparams1.alphaMean << " s.d. " << eparams1.alphaSD - << " scaling factor " << eparams1.alphaScale << endl; - outPar << "Beta females: mean " << eparams0.betaMean << " s.d. " << eparams0.betaSD - << " scaling factor " << eparams0.betaScale << endl; - outPar << "Beta males: mean " << eparams1.betaMean << " s.d. " << eparams1.betaSD - << " scaling factor " << eparams1.betaScale << endl; - } - else { - outPar << "no" << endl; - ep0 = pSpecies->getEmigTraits(0, 0); - ep1 = pSpecies->getEmigTraits(0, 1); + ep0 = pSpecies->getSpEmigTraits(0, 0); + ep1 = pSpecies->getSpEmigTraits(0, 1); outPar << "D0: females " << ep0.d0 << " males " << ep1.d0 << endl; outPar << "alpha: females " << ep0.alpha << " males " << ep1.alpha << endl; outPar << "beta: females " << ep0.beta << " males " << ep1.beta << endl; } } - } else { // !emig.sexDep outPar << sexdept << "no" << endl; if (emig.stgDep) { outPar << stgdept << "yes" << endl; outPar << indvar << "no" << endl; for (int i = 0; i < sstruct.nStages; i++) { - ep0 = pSpecies->getEmigTraits(i, 0); + ep0 = pSpecies->getSpEmigTraits(i, 0); outPar << "stage " << i << ": \t" << "D0: " << ep0.d0; outPar << " \talpha: " << ep0.alpha << " \tbeta: " << ep0.beta << endl; } } else { // !emig.stgDep outPar << stgdept << "no" << endl; - outPar << indvar; - if (emig.indVar) { - eparams0 = pSpecies->getEmigParams(0, 0); - emigScales scale = pSpecies->getEmigScales(); - outPar << "yes" << endl; - if (dem.stageStruct) { - outPar << emigstage << emig.emigStage << endl; - } - outPar << "D0 mean: " << eparams0.d0Mean << " s.d.: " << eparams0.d0SD - << " scaling factor: " << scale.d0Scale << endl; - outPar << "Alpha mean: " << eparams0.alphaMean << " s.d.: " << eparams0.alphaSD - << " scaling factor: " << scale.alphaScale << endl; - outPar << "Beta mean: " << eparams0.betaMean << " s.d.: " << eparams0.betaSD - << " scaling factor: " << scale.betaScale << endl; - } - else { - outPar << "no" << endl; - ep0 = pSpecies->getEmigTraits(0, 0); + ep0 = pSpecies->getSpEmigTraits(0, 0); outPar << "D0: " << ep0.d0 << endl; outPar << "alpha: " << ep0.alpha << endl; outPar << "beta: " << ep0.beta << endl; } } } - } else { // not density-dependent string initprob = "INITIAL EMIGRATION PROB. "; outPar << "density-independent" << endl; - if (!trfr.moveModel) { // transfer by kernel + if (!trfr.usesMovtProc) { // transfer by kernel outPar << "USE FULL KERNEL TO DETERMINE EMIGRATION: "; if (pSpecies->useFullKernel()) outPar << "yes"; else outPar << "no"; @@ -1389,34 +1355,15 @@ void OutParameters(Landscape* pLandscape) outPar << indvar << "no" << endl; for (int i = 0; i < sstruct.nStages; i++) { outPar << "stage " << i << ": \t" << "EMIGRATION PROB.: \tfemales " - << pSpecies->getEmigD0(i, 0) << " \tmales " << pSpecies->getEmigD0(i, 1) << endl; + << pSpecies->getSpEmigD0(i, 0) << " \tmales " << pSpecies->getSpEmigD0(i, 1) << endl; } } else { // !emig.stgDep outPar << stgdept << "no" << endl; - outPar << indvar; - if (emig.indVar) { - eparams0 = pSpecies->getEmigParams(0, 0); - eparams1 = pSpecies->getEmigParams(0, 1); - emigScales scale = pSpecies->getEmigScales(); - outPar << "yes" << endl; - if (dem.stageStruct) { - outPar << emigstage << emig.emigStage << endl; + outPar << "EMIGRATION PROB.: \tfemales " << pSpecies->getSpEmigD0(0, 0) + << "\t males " << pSpecies->getSpEmigD0(0, 1) << endl; } - outPar << initprob << "mean: " << "females " << eparams0.d0Mean - << " males " << eparams1.d0Mean << endl; - outPar << initprob << "s.d.: " << "females " << eparams0.d0SD - << " males " << eparams1.d0SD << endl; - outPar << initprob << "scaling factor: " << scale.d0Scale - << endl; } - else { - outPar << "no" << endl; - outPar << "EMIGRATION PROB.: \tfemales " << pSpecies->getEmigD0(0, 0) - << "\t males " << pSpecies->getEmigD0(0, 1) << endl; - } - } - } else { // !emig.sexDep outPar << sexdept << "no" << endl; if (emig.stgDep) { @@ -1424,45 +1371,27 @@ void OutParameters(Landscape* pLandscape) outPar << indvar << "no" << endl; for (int i = 0; i < sstruct.nStages; i++) { outPar << "stage " << i << ": \t" << "EMIGRATION PROB.: " - << pSpecies->getEmigD0(i, 0) << endl; + << pSpecies->getSpEmigD0(i, 0) << endl; } } else { // !emig.stgDep outPar << stgdept << "no" << endl; - outPar << indvar; - if (emig.indVar) { - eparams0 = pSpecies->getEmigParams(0, 0); - emigScales scale = pSpecies->getEmigScales(); - outPar << "yes" << endl; - if (dem.stageStruct) { - outPar << emigstage << emig.emigStage << endl; + outPar << "EMIGRATION PROB.:\t" << pSpecies->getSpEmigD0(0, 0) << endl; } - outPar << initprob << "mean: " << eparams0.d0Mean << endl; - outPar << initprob << "s.d.: " << eparams0.d0SD << endl; - outPar << initprob << "scaling factor: " << scale.d0Scale << endl; } - else { - outPar << "no" << endl; - outPar << "EMIGRATION PROB.:\t" << pSpecies->getEmigD0(0, 0) << endl; } - } - } - } // Transfer outPar << endl << "DISPERSAL - TRANSFER: \t"; - if (trfr.moveModel) { - bool straigtenPath; + if (trfr.usesMovtProc) { + bool straightenPath; if (trfr.moveType == 1) { // SMS - trfrSMSTraits move = pSpecies->getSMSTraits(); - straigtenPath = move.straigtenPath; + trfrSMSTraits move = pSpecies->getSpSMSTraits(); + straightenPath = move.straightenPath; if (trfr.costMap) { outPar << "SMS\tcosts from imported cost map" << endl; -#if !RS_RCPP - outPar << "FILE NAME: " << costmapname << endl; -#endif } else { outPar << "SMS\tcosts:" << endl; @@ -1490,48 +1419,19 @@ void OutParameters(Landscape* pLandscape) outPar << "BETA DB: " << move.betaDB << endl; } } - if (trfr.indVar) { - trfrSMSParams s = pSpecies->getSMSParams(0, 0); - outPar << indvar << "yes " << endl; - outPar << "DP mean: " << s.dpMean << " s.d.: " << s.dpSD - << " scaling factor: " << s.dpScale << endl; - outPar << "GB mean: " << s.gbMean << " s.d.: " << s.gbSD - << " scaling factor: " << s.gbScale << endl; - if (move.goalType == 2) { // dispersal bias - outPar << "Alpha DB mean: " << s.alphaDBMean << " s.d.: " << s.alphaDBSD - << " scaling factor: " << s.alphaDBScale << endl; - outPar << "Beta DB mean: " << s.betaDBMean << " s.d.: " << s.betaDBSD - << " scaling factor: " << s.betaDBScale << endl; - } - } - else { outPar << indvar << "no " << endl; } - } else { // CRW - trfrCRWTraits move = pSpecies->getCRWTraits(); - straigtenPath = move.straigtenPath; + trfrCRWTraits move = pSpecies->getSpCRWTraits(); + straightenPath = move.straightenPath; outPar << "CRW" << endl; string lgth = "STEP LENGTH (m) "; string corr = "STEP CORRELATION"; - if (trfr.indVar) { - trfrCRWParams m = pSpecies->getCRWParams(0, 0); - outPar << indvar << "yes" << endl; - outPar << lgth << " mean: " << m.stepLgthMean; - outPar << " s.d.: " << m.stepLgthSD; - outPar << " scaling factor: " << m.stepLScale << endl; - outPar << corr << " mean: " << m.rhoMean; - outPar << " s.d.: " << m.rhoSD; - outPar << " scaling factor: " << m.rhoScale << endl; - } - else { - outPar << indvar << "no" << endl; outPar << lgth << ": " << move.stepLength << endl; outPar << corr << ": " << move.rho << endl; } - } outPar << "STRAIGHTEN PATH AFTER DECISION NOT TO SETTLE: "; - if (straigtenPath) outPar << "yes" << endl; + if (straightenPath) outPar << "yes" << endl; else outPar << "no" << endl; outPar << "STEP MORTALITY:\t" << endl; if (trfr.habMort) @@ -1550,15 +1450,14 @@ void OutParameters(Landscape* pLandscape) } else { - trfrCRWTraits move = pSpecies->getCRWTraits(); + trfrCRWTraits move = pSpecies->getSpCRWTraits(); outPar << "constant " << move.stepMort << endl; } } // end of movement process else { // kernel string meandist = "MEAN DISTANCE"; string probkern = "PROB. KERNEL I"; - trfrKernTraits kern0, kern1; - trfrKernParams k0, k1; + trfrKernelParams kern0, kern1; outPar << "dispersal kernel" << endl << "TYPE: \t"; if (trfr.twinKern) outPar << "double "; outPar << "negative exponential" << endl; @@ -1570,8 +1469,8 @@ void OutParameters(Landscape* pLandscape) outPar << indvar << "no" << endl; for (int i = 0; i < sstruct.nStages; i++) { outPar << "stage " << i << ":" << endl; - kern0 = pSpecies->getKernTraits(i, 0); - kern1 = pSpecies->getKernTraits(i, 1); + kern0 = pSpecies->getSpKernTraits(i, 0); + kern1 = pSpecies->getSpKernTraits(i, 1); outPar << meandist << " I: \tfemales " << kern0.meanDist1 << " \tmales " << kern1.meanDist1 << endl; if (trfr.twinKern) { @@ -1582,37 +1481,8 @@ void OutParameters(Landscape* pLandscape) } else { // !trfr.stgDep outPar << stgdept << "no" << endl; - outPar << indvar; - if (trfr.indVar) { - k0 = pSpecies->getKernParams(0, 0); - k1 = pSpecies->getKernParams(0, 1); - outPar << "yes" << endl; - outPar << meandist << " I (mean): \tfemales " << k0.dist1Mean - << " \tmales " << k1.dist1Mean << endl; - outPar << meandist << " I (s.d.): \tfemales " << k0.dist1SD - << " \tmales " << k1.dist1SD << endl; - outPar << meandist << " I (scaling factor): \tfemales " << k0.dist1Scale - << " \tmales " << k1.dist1Scale << endl; - if (trfr.twinKern) - { - outPar << meandist << " II (mean): \tfemales " << k0.dist2Mean - << " \tmales " << k1.dist2Mean << endl; - outPar << meandist << " II (s.d.): \tfemales " << k0.dist2SD - << " \tmales " << k1.dist2SD << endl; - outPar << meandist << " II (scaling factor): \tfemales " << k0.dist2Scale - << " \tmales " << k1.dist2Scale << endl; - outPar << probkern << " (mean): \tfemales " << k0.PKern1Mean - << " \tmales " << k1.PKern1Mean << endl; - outPar << probkern << " (s.d.): \tfemales " << k0.PKern1SD - << " \tmales " << k1.PKern1SD << endl; - outPar << probkern << " (scaling factor): \tfemales " << k0.PKern1Scale - << " \tmales " << k1.PKern1Scale << endl; - } - } - else { - outPar << "no" << endl; - kern0 = pSpecies->getKernTraits(0, 0); - kern1 = pSpecies->getKernTraits(0, 1); + kern0 = pSpecies->getSpKernTraits(0, 0); + kern1 = pSpecies->getSpKernTraits(0, 1); outPar << meandist << " I: \tfemales " << kern0.meanDist1 << " \tmales " << kern1.meanDist1 << endl; if (trfr.twinKern) { @@ -1621,14 +1491,13 @@ void OutParameters(Landscape* pLandscape) } } } - } else { // !trfr.sexDep outPar << sexdept << "no" << endl; if (trfr.stgDep) { outPar << stgdept << "yes" << endl; outPar << indvar << "no" << endl; for (int i = 0; i < sstruct.nStages; i++) { - kern0 = pSpecies->getKernTraits(i, 0); + kern0 = pSpecies->getSpKernTraits(i, 0); outPar << "stage " << i << ": \t" << meandist << " I: " << kern0.meanDist1; if (trfr.twinKern) { @@ -1640,26 +1509,7 @@ void OutParameters(Landscape* pLandscape) } else { // !trfr.stgDep outPar << stgdept << "no" << endl; - outPar << indvar; - if (trfr.indVar) { - k0 = pSpecies->getKernParams(0, 0); - outPar << "yes" << endl; - outPar << meandist << " I (mean): " << k0.dist1Mean - << " \t(s.d.): " << k0.dist1SD - << " \t(scaling factor): " << k0.dist1Scale << endl; - if (trfr.twinKern) - { - outPar << meandist << " II (mean): " << k0.dist2Mean - << " \t(s.d.): " << k0.dist2SD - << " \t(scaling factor): " << k0.dist2Scale << endl; - outPar << probkern << " (mean): " << k0.PKern1Mean - << " \t(s.d.): " << k0.PKern1SD - << " \t(scaling factor): " << k0.PKern1Scale << endl; - } - } - else { - outPar << "no" << endl; - kern0 = pSpecies->getKernTraits(0, 0); + kern0 = pSpecies->getSpKernTraits(0, 0); outPar << meandist << " I: \t" << kern0.meanDist1 << endl; if (trfr.twinKern) { @@ -1668,7 +1518,6 @@ void OutParameters(Landscape* pLandscape) } } } - } outPar << "DISPERSAL MORTALITY: "; trfrMortParams mort = pSpecies->getMortParams(); @@ -1685,7 +1534,7 @@ void OutParameters(Landscape* pLandscape) outPar << endl << "DISPERSAL - SETTLEMENT:" << endl; - if (trfr.moveModel) { + if (trfr.usesMovtProc) { string plusmating = "+ mating requirements"; if (sett.sexDep) { @@ -1761,7 +1610,7 @@ void OutParameters(Landscape* pLandscape) outPar << "find a suitable cell/patch "; srules = pSpecies->getSettRules(i, sx); if (srules.densDep) { - settleDD = pSpecies->getSettTraits(i, sx); + settleDD = pSpecies->getSpSettTraits(i, sx); outPar << "+ density dependence "; if (srules.findMate) outPar << plusmating; outPar << endl; @@ -1782,25 +1631,7 @@ void OutParameters(Landscape* pLandscape) } } } - if (sett.indVar) { - settParams sparams0; - outPar << "DENSITY DEPENDENCE + " << indvar << "yes" << endl; - for (int sex = 0; sex < nsexes; sex++) { - if (sett.sexDep) { - if (sex == 0) outPar << "FEMALES:" << endl; - else outPar << "MALES:" << endl; } - sparams0 = pSpecies->getSettParams(0, sex); - settScales scale = pSpecies->getSettScales(); - outPar << "S0 - mean: " << sparams0.s0Mean << " s.d.: " << sparams0.s0SD - << " scaling factor: " << scale.s0Scale << endl; - outPar << "AlphaS - mean: " << sparams0.alphaSMean << " s.d.: " << sparams0.alphaSSD - << " scaling factor: " << scale.alphaSScale << endl; - outPar << "BetaS - mean: " << sparams0.betaSMean << " s.d.: " << sparams0.betaSSD - << " scaling factor: " << scale.betaSScale << endl; - } - } - } else { // kernel-based transfer string notsuit = "IF THE ARRIVAL CELL/PATCH IS UNSUITABLE: "; string rchoose = " randomly choose a suitable neighb. cell/patch or "; @@ -1860,72 +1691,65 @@ void OutParameters(Landscape* pLandscape) } // Genetics - outPar << endl << "GENETICS:" << endl; - int nspptraits = pSpecies->getNTraits(); - outPar << "No. of variable traits: " << nspptraits << endl; - genomeData d = pSpecies->getGenomeData(); - if (emig.indVar || trfr.indVar || sett.indVar || d.neutralMarkers) - { - if (d.diploid) outPar << "DIPLOID" << endl; else outPar << "HAPLOID" << endl; - int nchromosomes = pSpecies->getNChromosomes(); - outPar << "No. of chromosomes: " << nchromosomes; - if (d.trait1Chromosome) { - outPar << endl << "No. of loci/chromosome: " << d.nLoci << endl; - } - else { - outPar << " (chrom:loci)"; - for (int i = 0; i < nchromosomes; i++) { - outPar << " " << i << ":" << pSpecies->getNLoci(i); - } + // only if genetics are simulated + if(sim.outputGenes) { + set traitList = pSpecies->getTraitTypes(); + + if (pSpecies->isDiploid()) outPar << "DIPLOID" << endl; else outPar << "HAPLOID" << endl; + outPar << "Genome size: " << pSpecies->getGenomeSize() << endl; + outPar << "Chromosome breaks : "; + + for (auto end : pSpecies->getChromosomeEnds()) + outPar << end << " "; outPar << endl; - } - outPar << "Mutation probability: " << d.probMutn << endl; - outPar << "Crossover probability: " << d.probCrossover << endl; - outPar << "Initial allele s.d.: " << d.alleleSD << endl; - outPar << "Mutation s.d.: " << d.mutationSD << endl; - if (d.neutralMarkers) { - outPar << "NEUTRAL MARKERS ONLY" << endl; - } - else { - if (!d.trait1Chromosome) { - traitAllele allele; - outPar << "TRAIT MAPPING:" << endl; - outPar << "Architecture file: " << genfilename << endl; - int ntraitmaps = pSpecies->getNTraitMaps(); - outPar << "No. of traits defined: " << ntraitmaps << endl; - for (int i = 0; i < ntraitmaps; i++) { - int nalleles = pSpecies->getNTraitAlleles(i); - outPar << "Trait " << i << ": (" << pSpecies->getTraitName(i) - << ") alleles: " << nalleles << " (chrom:locus)"; - for (int j = 0; j < nalleles; j++) { - allele = pSpecies->getTraitAllele(i, j); - outPar << " " << allele.chromo << ":" << allele.locus; - } - outPar << endl; - } - if (ntraitmaps < nspptraits) { // list undefined traits - outPar << "WARNING - the following traits were not defined" - << " in the genetic architecture file:" << endl; - for (int i = ntraitmaps; i < nspptraits; i++) { - outPar << "Trait " << i << ": (" << pSpecies->getTraitName(i) - << ") all individuals have mean phenotype" << endl; - } - } - int nneutral = pSpecies->getNNeutralLoci(); - if (nneutral > 0) { - outPar << "Neutral loci: " << nneutral << " (chrom:locus)"; - for (int i = 0; i < nneutral; i++) { - allele = pSpecies->getNeutralAllele(i); - outPar << " " << allele.chromo << ":" << allele.locus; - } - outPar << endl; - } - if (d.pleiotropic) - outPar << "Genome exhibits pleiotropy" << endl; - } - } + outPar << "Recombination rate: " << pSpecies->getRecombinationRate() << endl; + outPar << "Traits modelled: " << endl; + for (auto trait : traitList) + outPar << trait << endl; + } else { + outPar << "No genetics simulated" << endl; + } + + + // Management + managementParams manage = pManagement->getManagementParams(); + translocationParams transloc = pManagement->getTranslocationParams(); + if(manage.translocation){ + outPar << endl << "MANAGEMENT - TRANSLOCATION: \t"; + // loop over translocation_years and print them + outPar << endl; + outPar << "Catching rate: " << transloc.catching_rate << endl; + for( int i = 0; i < transloc.translocation_years.size(); i++ ) { + auto yr = transloc.translocation_years[i]; + auto it = transloc.nb.find(yr); + auto nb_it = transloc.nb.find(yr); + auto source_it = transloc.source.find(yr); + auto target_it = transloc.target.find(yr); + auto min_age_it = transloc.min_age.find(yr); + auto max_age_it = transloc.max_age.find(yr); + auto stage_it = transloc.stage.find(yr); + auto sex_it = transloc.sex.find(yr); + outPar << " Translocation events in year: " << yr << endl; + for( int j = 0; j < it->second.size(); j++ ){ + outPar << " Event Nr. " << j+1 << " :" << endl; + // if it is a cell based model + if(ppLand.patchModel){ + outPar << " Source patch ID: " << source_it->second[j].x << endl; + outPar << " Target patch ID: " << target_it->second[j].x << endl; + } else{ + outPar << " Source cell: X " << source_it->second[j].x << " Y " << source_it->second[j].y << endl; + outPar << " Target cell: X " << target_it->second[j].x << " Y " << target_it->second[j].y << endl; + } + outPar << " Min age: " << min_age_it->second[j] << endl; + outPar << " Max age: " << max_age_it->second[j] << endl; + outPar << " Stage: " << stage_it->second[j] << endl; + outPar << " Sex: " << sex_it->second[j] << endl; + outPar << " Number of individuals: " << nb_it->second[j] << endl; + + } + } } // Initialisation @@ -2010,6 +1834,9 @@ void OutParameters(Landscape* pLandscape) outPar << "GEOGRAPHICAL CONSTRAINTS (cell numbers): " << endl; outPar << "min X: " << init.minSeedX << " max X: " << init.maxSeedX << endl; outPar << "min Y: " << init.minSeedY << " max Y: " << init.maxSeedY << endl; + // if (init.seedType != 1 && init.freeType < 2 && init.initFrzYr > 0) { + // outPar << "Freeze initial range until year " << init.initFrzYr << endl; + // } if (init.seedType == 0 && init.freeType < 2) { if (init.initFrzYr > 0) { outPar << "Freeze initial range until year " << init.initFrzYr << endl; @@ -2028,11 +1855,13 @@ void OutParameters(Landscape* pLandscape) if (sim.outRange) { outPar << "Range - every " << sim.outIntRange << " year"; if (sim.outIntRange > 1) outPar << "s"; + // if (sim.outStartRange > 0) outPar << " starting year " << sim.outStartRange; outPar << endl; } if (sim.outOccup) { outPar << "Occupancy - every " << sim.outIntOcc << " year"; if (sim.outIntOcc > 1) outPar << "s"; + // if (sim.outStartOcc > 0) outPar << " starting year " << sim.outStartOcc; outPar << endl; } if (sim.outPop) { @@ -2047,27 +1876,20 @@ void OutParameters(Landscape* pLandscape) if (sim.outStartInd > 0) outPar << " starting year " << sim.outStartInd; outPar << endl; } - if (sim.outGenetics) { - outPar << "Genetics - every " << sim.outIntGenetic << " year"; - if (sim.outIntGenetic > 1) outPar << "s"; - if (sim.outStartGenetic > 0) outPar << " starting year " << sim.outStartGenetic; - if (dem.stageStruct) { - switch (sim.outGenType) { - case 0: - outPar << " - juveniles only"; - break; - case 1: - outPar << " - all individuals"; - break; - case 2: - outPar << " - adults only"; - break; - } - } - if (sim.outGenXtab) outPar << " (as cross table)"; + if (sim.outputGlobalFst) { + outPar << "Global Fst and neutral genetics - every " << sim.outputGlobalFstInterval << " year"; + if (sim.outputGlobalFstInterval > 1) outPar << "s"; + if (sim.outputPerLocusFst) outPar << "outputting per locus Fst too"; + outPar << endl; + } + + if (sim.outPairwiseFst) { + outPar << "Pairwise Fst - every " << sim.outputPairwiseFstInterval << " year"; + if (sim.outputPairwiseFstInterval > 1) outPar << "s"; outPar << endl; } + if (sim.outTraitsCells) { outPar << "Traits per "; if (ppLand.patchModel) outPar << "patch"; else outPar << "cell"; @@ -2096,28 +1918,13 @@ void OutParameters(Landscape* pLandscape) outPar << endl; } #endif - outPar << "SAVE MAPS: "; - if (sim.saveMaps) { - outPar << "yes - every " << sim.mapInt << " year"; - if (sim.mapInt > 1) outPar << "s"; - outPar << endl; - } - else outPar << "no" << endl; - outPar << "SAVE TRAITS MAPS: "; - if (sim.saveTraitMaps) { - outPar << "yes - every " << sim.traitInt << " year"; - if (sim.traitInt > 1) outPar << "s"; - outPar << endl; - } - else outPar << "no" << endl; - if (trfr.moveModel && trfr.moveType == 1) { + + if (trfr.usesMovtProc && trfr.moveType == 1) { outPar << "SMS HEAT MAPS: "; if (sim.saveVisits) outPar << "yes" << endl; else outPar << "no" << endl; } - outPar.close(); outPar.clear(); - } //--------------------------------------------------------------------------- diff --git a/Model.h b/Model.h index f48d155..61474c0 100644 --- a/Model.h +++ b/Model.h @@ -1,25 +1,25 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - + + /*------------------------------------------------------------------------------ RangeShifter v2.0 Model @@ -33,14 +33,14 @@ Further functions are declared here, but defined differently in main function of GUI and batch versions. For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. + Bocedi G., Palmer S.C.F., Pe’er G., Heikkinen R.K., Matsinos Y.G., Watts K. and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. + eco-evolutionary dynamics and species’ responses to environmental changes. Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 Authors: Greta Bocedi & Steve Palmer, University of Aberdeen -Last updated: 26 October 2021 by Steve Palmer + Last updated: 28 July 2021 by Greta Bocedi ------------------------------------------------------------------------------*/ #ifndef ModelH @@ -48,26 +48,29 @@ Last updated: 26 October 2021 by Steve Palmer #include #include +#if RS_RCPP +#include +#include "../Rinterface.h" +#endif // RS_RCPP +#include #include "Parameters.h" #include "Landscape.h" #include "Community.h" #include "SubCommunity.h" #include "Species.h" +#include "Management.h" -#if !RS_EMBARCADERO && !LINUX_CLUSTER && !RS_RCPP +#if !LINUX_CLUSTER && !RS_RCPP #include using namespace std::filesystem; #endif -#if RSDEBUG -extern ofstream DEBUGLOG; -#endif - #if RS_RCPP && !R_CMD Rcpp::List RunModel( Landscape*, // pointer to Landscape - int // sequential simulation number + int, // sequential simulation number + Rcpp::S4 // parameter master to read initial conditions in each replicate simulation ); #else int RunModel( @@ -75,7 +78,9 @@ int RunModel( int // sequential simulation number ); #endif // RS_RCPP && !R_CMD -bool CheckDirectory(void); + +bool CheckDirectory(const string& pathToProjDir); + void PreReproductionOutput( Landscape*, // pointer to Landscape Community*, // pointer to Community @@ -89,20 +94,7 @@ void RangePopOutput( int, // year int // generation ); -void Outputs_Visuals_B( - int, // replicate - int, // year - int, // generation - int // Landscape number -); -void RefreshVisualCost(void); -traitCanvas SetupTraitCanvas(void); -void SetupVisualOutput(void); -void ResetVisualOutput(void); -void DrawPopnGraph( - Community*, // pointer to Community - int // year -); + void OutParameters( Landscape* // pointer to Landscape ); @@ -113,26 +105,11 @@ extern Species *pSpecies; extern paramSim *paramsSim; extern paramInit *paramsInit; extern Community *pComm; +extern Management *pManagement; -const bool batchMode = true; extern string landFile; -extern vector hfnames; -extern string habmapname; // see Main.cpp (batch) -extern string patchmapname; // see Main.cpp (batch) -extern string distnmapname; // see Main.cpp (batch) -extern string costmapname; // see Main.cpp (batch) -extern string genfilename; // see Main.cpp (batch) extern RSrandom *pRandom; -// these functions to have different version for GUI and batch applications ... -#if BATCH -extern void MemoLine(string); -#endif -void GUIsetLandScale( - int, // landscape image height (pixels) - int // landscape image width (pixels) -); - #if RS_RCPP extern std::uint32_t RS_random_seed; extern string name_landscape, name_patch, name_costfile, name_sp_dist; diff --git a/NeutralStatsManager.cpp b/NeutralStatsManager.cpp new file mode 100644 index 0000000..6acdc19 --- /dev/null +++ b/NeutralStatsManager.cpp @@ -0,0 +1,592 @@ +#include "NeutralStatsManager.h" +#include "Population.h" + +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + + // ---------------------------------------------------------------------------------------- + // Constructor + // ---------------------------------------------------------------------------------------- +NeutralStatsManager::NeutralStatsManager(const int& nbSampledPatches, const int nLoci) { + this->pairwiseFstMatrix = PatchMatrix(nbSampledPatches, nbSampledPatches); + commNeutralCountTables.reserve(nLoci); //don't have to be pointers, not shared or moved + + perLocusFst = perLocusFis = perLocusFit = vector(nLoci, 0.0); +} + +// ---------------------------------------------------------------------------------------- +// Populate population and community-level NEUTRAL count tables +// Update allele occurrence and heterozygosity counts, and allele frequencies +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::updateAllNeutralTables(Species* pSpecies, Landscape* pLandscape, set const& patchList) { + + const int nLoci = pSpecies->getNPositionsForTrait(NEUTRAL); + const int maxNbNeutralAlleles = pSpecies->getSpTrait(NEUTRAL)->getNbNeutralAlleles(); + const int ploidy = pSpecies->isDiploid() ? 2 : 1; + + // Create / Update community-level NEUTRAL counts table + if (!commNeutralCountTables.empty()) { + resetCommNeutralTables(); + } + else { // populate the tables with default values + for (int thisLocus = 0; thisLocus < nLoci; thisLocus++) { + NeutralCountsTable newNeutralTbl = NeutralCountsTable(maxNbNeutralAlleles); + commNeutralCountTables.push_back(newNeutralTbl); + } + } + + int nbSampledInds = 0; + int patchAlleleCount; + + // Update counts for each population + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + // Update this population's NEUTRAL counts tables + pPop->updatePopNeutralTables(); + nbSampledInds += pPop->sampleSize(); + } + // Add population-level counts to community-level counts + for (int thisLocus = 0; thisLocus < nLoci; thisLocus++) { + for (int allele = 0; allele < maxNbNeutralAlleles; allele++) { + + if (pPop != 0) { + patchAlleleCount = pPop->getAlleleTally(thisLocus, allele); + } + else { + patchAlleleCount = 0; + } + commNeutralCountTables[thisLocus].incrementTallyBy(patchAlleleCount, allele); + } + } + } + + // Update community-level frequencies + std::for_each(commNeutralCountTables.begin(), + commNeutralCountTables.end(), + [&](NeutralCountsTable& v) -> void { + v.setFrequencies(nbSampledInds * ploidy); + }); +} + +// ---------------------------------------------------------------------------------------- +// Reset allele tables in NeutralTable structs +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::resetCommNeutralTables() { + for (auto& entry : commNeutralCountTables) { + entry.reset(); + } +} + +// ---------------------------------------------------------------------------------------- +// Calculate allelic diversity metrics +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::calcAllelicDiversityMetrics(set const& patchList, const int nInds, Species* pSpecies, Landscape* pLandscape) +{ + int i, j; + const int nLoci = pSpecies->getNPositionsForTrait(NEUTRAL); + const int nAlleles = pSpecies->getSpTrait(NEUTRAL)->getNbNeutralAlleles(); + const int ploidy = pSpecies->isDiploid() ? 2 : 1; + unsigned int nbPops = 0; + int nbAllelesInPatch = 0; + double meanAllelicDivInPatch = 0; + bool alleleExistsInPop = 0; + + vector> alleleExistsInCommTable(nLoci); + for (i = 0; i < nLoci; ++i) { + alleleExistsInCommTable[i] = vector(nAlleles, false); + } + + // Compute mean nb alleles per locus per patch + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + if (pPop->sampleSize() > 0) { + nbPops++; + nbAllelesInPatch = 0; + for (i = 0; i < nLoci; ++i) + for (j = 0; j < nAlleles; ++j) { + alleleExistsInPop = pPop->getAlleleTally(i, j) != 0; + nbAllelesInPatch += alleleExistsInPop; + alleleExistsInCommTable[i][j] = alleleExistsInCommTable[i][j] || alleleExistsInPop; + } + // add mean nb of alleles per locus for Patch k to the pop mean + meanAllelicDivInPatch += static_cast(nbAllelesInPatch) / nLoci; + } + } + } + meanNbAllelesPerLocusPerPatch = nbPops > 0 ? meanAllelicDivInPatch / nbPops : 0; + + // Compute mean nb alleles per locus + meanNbAllelesPerLocus = 0; + for (i = 0; i < nLoci; ++i) + for (j = 0; j < nAlleles; ++j) + meanNbAllelesPerLocus += alleleExistsInCommTable[i][j]; + meanNbAllelesPerLocus /= nLoci; + + // Compute number of fixed loci per patch + // mean number of loci that are fixed at pop level per pop + meanNbFixedLociPerPatch = 0; + if (nbPops > 0) { + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + for (i = 0; i < nLoci; ++i) + for (j = 0; j < nAlleles; ++j) + meanNbFixedLociPerPatch += pPop->getAlleleFrequency(i, j) == 1; + } + } + meanNbFixedLociPerPatch /= nbPops; + } + + // Compute number of fixed loci + meanFixedLoci = 0; + for (i = 0; i < nLoci; ++i) + for (j = 0; j < nAlleles; ++j) + meanFixedLoci += commNeutralCountTables[i].getFrequency(j) == 1; +} + +// ---------------------------------------------------------------------------------------- +// Calculate Ho per Nei and Chesser +// Average (observed) heterozygosity per individual +// Sum (nb of heterozygote loci) across individuals / nb individuals / nb loci +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::calculateHo(set const& patchList, const int nbInds, const int nbrLoci, Species* pSpecies, Landscape* pLandscape) { + + int nbHetero = 0; + + if (nbInds != 0 && pSpecies->isDiploid()) { + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) nbHetero += pPop->countHeterozygoteLoci(); + } + ho = static_cast(nbHetero) / (nbInds * nbrLoci); + } + else ho = 0.0; +} + +// ---------------------------------------------------------------------------------------- +// Calculate Hs per Nei and Chesser +// Average expected population-level heterozygosity per locus per population +// currently not used but may be useful +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::calculateHs(set const& patchList, const int nbrLoci, Species* pSpecies, Landscape* pLandscape) { + + double hs = 0; + int nPatches = 0; + + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop->sampleSize() > 0) { + nPatches++; + hs += pPop->computeHs(); + } + } + hs = (nPatches != 0 ? hs / (nbrLoci * nPatches) : 0.0); +} + +// ---------------------------------------------------------------------------------------- +// Calculate Ht per Nei and Chesser +// Average expected community-level heterozygosity per locus +// Currently not used but may be useful +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::calculateHt(Species* pSpecies, Landscape* pLandscape, const int nLoci, const int nAlleles) { + + double ht = 0; + int nPatches = 0; + vectorlocihet(nLoci, 1); + double freq; + + for (int thisLocus = 0; thisLocus < nLoci; ++thisLocus) { + for (int allele = 0; allele < nAlleles; ++allele) { + freq = commNeutralCountTables[thisLocus].getFrequency(allele); + freq *= freq; //squared frequencies + locihet[thisLocus] -= freq; //1 - sum of p2 = expected heterozygosity + } + ht += locihet[thisLocus]; + } + ht = ht / nLoci; +} + +// ---------------------------------------------------------------------------------------- +// Calculate Ho per locus as per Nei and Chesser +// Observed proportion of heterozygote individuals for each locus +// Sum (nb of heterozygote individuals) / nb individuals for each locus +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::calculatePerLocusHo(set const& patchList, const int nbInds, const int nbrLoci, Species* pSpecies, Landscape* pLandscape) { + + vector nbHeterosInComm(nbrLoci, 0); + vector nbHeterosInPop(nbrLoci); + + if (pSpecies->isDiploid()) { + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + if (pPop->sampleSize() > 0) { + nbHeterosInPop = pPop->countNbHeterozygotesEachLocus(); + // Add counts to community total + transform(nbHeterosInComm.begin(), nbHeterosInComm.end(), nbHeterosInPop.begin(), + nbHeterosInComm.begin(), plus()); + } + } + } + } + + perLocusHo = vector(nbrLoci, 0); + if (nbInds != 0) { + for (int i = 0; i < nbHeterosInComm.size(); i++) { + perLocusHo[i] = static_cast(nbHeterosInComm[i]) / nbInds; + } + } +} + +// ---------------------------------------------------------------------------------------- +// Fstat Weir & Cockerham +// ---------------------------------------------------------------------------------------- +void NeutralStatsManager::calculateFstatWC(set const& patchList, const int nbSampledIndsInComm, const int nLoci, const int nAlleles, + Species* pSpecies, Landscape* pLandscape, bool isPairwise) { + + double inverseNtotal; + double sumWeights = 0; + double nBar, nC, inverseNbar; + unsigned int nbPops = 0; + const int totalSampleSize = nbSampledIndsInComm; // r * n_bar + const double ploidy = pSpecies->isDiploid() ? 2.0 : 1.0; + + + // Reset per-locus vectors between generations + perLocusFst = perLocusFis = perLocusFit = vector(nLoci, 0.0); + + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + int popSampleSize = pPop->sampleSize(); // n_i + if (popSampleSize > 0) { + nbPops++; + sumWeights += static_cast(popSampleSize * popSampleSize) / totalSampleSize; // sum(n_i^2/rn_bar) + } + } + } + + nbExtantPops = nbPops; // r + totalNbSampledInds = nbSampledIndsInComm; + + if (nbPops > 1) { + + // Calculate F stats + nBar = static_cast(totalSampleSize) / nbPops; // average sample size, cannot be less than 1 + nC = (totalSampleSize - sumWeights) / (nbPops - 1); + double nBarMinusOne = (nBar == 1.0) ? 1.0 : nBar - 1.0; // avoid / 0 if exactly 1 ind per pop + inverseNbar = 1.0 / nBarMinusOne; + inverseNtotal = 1.0 / totalSampleSize; + + double var, intermediateTerm; + double s2, pBar, hBar; + int pairwiseAlleleCount; + double s2Denom = (nbPops - 1) * nBar; + double rTerm = static_cast(nbPops - 1) / nbPops; + double hBarFactor = (2 * nBar - 1) / (4 * nBar); + + double numFst = 0.0, numFis = 0.0, numFit = 0.0; + double denomFst = 0.0, denomFis = 0.0, denomFit = 0.0; + + for (int l = 0; l < nLoci; ++l) { + + // Sums of a_u, b_u, c_u for all alleles u at locus l + double a_l = 0, b_l = 0, c_l = 0; + + for (int u = 0; u < nAlleles; ++u) { + + pBar = s2 = hBar = 0; + pairwiseAlleleCount = 0; + + //if global wc approach use this + if (!isPairwise) { + pBar = commNeutralCountTables[l].getFrequency(u); + } + //else calculate total frequencies just in pair of patches for pairwise + else { + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + double patchLocusAlleleTally = pPop->getAlleleTally(l, u); + pairwiseAlleleCount += patchLocusAlleleTally; + } + const double denomAlleleCount = totalSampleSize * ploidy; + pBar = pairwiseAlleleCount / denomAlleleCount; + + } + + for (int patchId : patchList) { + const auto patch = pLandscape->findPatch(patchId); + const auto pPop = patch->getPopn(pSpecies); + if (pPop != 0) { + var = pPop->getAlleleFrequency(l, u) - pBar; + var *= var; + s2 += var * pPop->sampleSize(); + hBar += pPop->getHeteroTally(l, u); // n_i * h_i + + } + } //end for pop + + s2 /= s2Denom; + hBar /= static_cast(totalSampleSize); // / (r * n_bar) + + intermediateTerm = pBar * (1 - pBar) - rTerm * s2; + a_l += s2 - inverseNbar * (intermediateTerm - 0.25 * hBar); + b_l += intermediateTerm - hBarFactor * hBar; + c_l += hBar; + + } // end for allele + + a_l *= nBar / nC; + b_l *= nBar / nBarMinusOne; + c_l *= 0.5; + + perLocusFst[l] = a_l / (a_l + b_l + c_l); + perLocusFis[l] = (b_l + c_l) == 0.0 ? 0.0 : b_l / (b_l + c_l); + perLocusFit[l] = a_l + b_l / (a_l + b_l + c_l); + + numFst += a_l; + numFis += b_l; + numFit += a_l + b_l; + denomFst += a_l + b_l + c_l; + denomFis += b_l + c_l; + + } // end for locus + + denomFit = denomFst; // same quantity + + fst = numFst / denomFst; // theta hat in eq. 1 in WC 1984 + fis = (denomFis == 0.0) ? 0.0 : numFis / denomFis; // f hat + fit = numFit / denomFit; // F hat + } + else { // zero or one sampled pops, cannot compute F-stats + fst = 0.0; + fis = 0.0; + fit = 0.0; + } +} + +////////New pairwise function /////////// + + +void NeutralStatsManager::calculatePairwiseFst(set const& patchList, const int nLoci, const int nAlleles, Species* pSpecies, Landscape* pLandscape) { + + // Needs to be in vector to iterate over, copy preserves order + vector patchVect; + copy(patchList.begin(), patchList.end(), std::back_inserter(patchVect)); + + const int nPatches = static_cast(patchVect.size()); + + // Initialise + pairwiseFstMatrix = PatchMatrix(nPatches, nPatches); + + // Reset table + pairwiseFstMatrix.setAll(0.0); // or nanf("NULL")? + + + for (int i = 0; i < nPatches - 1; ++i) + { + const auto patchA = pLandscape->findPatch(patchVect[i]); + const auto pPopA = patchA->getPopn(pSpecies); + + // Skip if patch A has no individuals + if (pPopA->sampleSize() == 0) + continue; + + for (int j = i + 1; j < nPatches; ++j) + { + const auto patchB = pLandscape->findPatch(patchVect[j]); + const auto pPopB = patchB->getPopn(pSpecies); + + // Skip if patch B has no individuals + if (pPopB->sampleSize() == 0) + continue; + + // Build pair-of-patches list + set pairofPatchesList; + pairofPatchesList.insert(patchVect[i]); + pairofPatchesList.insert(patchVect[j]); + + // Total individuals in the pair + int nbSampledIndsInPair = pPopA->sampleSize() + pPopB->sampleSize(); + + //NB this overwrites global fst variable so be careful!! + calculateFstatWC(pairofPatchesList, nbSampledIndsInPair, + nLoci, nAlleles,pSpecies, pLandscape, true); + + pairwiseFstMatrix.set(i, j, fst); + // else remain 0 + } + } + +} + + + +// ---------------------------------------------------------------------------------------- +// Patch pairwise Fst +// Computes the weighted within and between patch Fst's as well as the overall Fst (Theta). +// The method used here is that of Weir & Hill 2002, Ann.Rev.Genet. 36:721 - 750. +// The weighting is done for samples(patches) of unequal sizes. +// ---------------------------------------------------------------------------------------- +//void NeutralStatsManager::calcPairwiseWeightedFst(set const& patchList, const int nInds, const int nLoci, Species* pSpecies, Landscape* pLandscape) { +// +// const int nAlleles = (int)pSpecies->getSpTrait(NEUTRAL)->getNbNeutralAlleles(); +// +// // Needs to be in vector to iterate over, copy preserves order +// vector patchVect; +// copy(patchList.begin(), patchList.end(), std::back_inserter(patchVect)); +// +// int nPatches = static_cast(patchList.size()); +// int nbPops = 0; +// +// // Initialise +// pairwiseFstMatrix = PatchMatrix(nPatches, nPatches); +// +// // Reset table +// pairwiseFstMatrix.setAll(0.0); // or nanf("NULL")? +// +// //init +// vector popWeights(nPatches); +// vector popSizes(nPatches); +// vector> numeratorPairwiseFst(nPatches); +// for (int i = 0; i < nPatches; i++) numeratorPairwiseFst[i].resize(nPatches); +// double totSize; +// double numeratorWeightedFst = 0; +// double denominator = 0; +// double sumWeights = 0; +// +// totalNbSampledInds = nInds; +// totSize = nInds; +// +// // Calculate weight (n_ic) terms +// for (int i = 0; i < nPatches; ++i) { +// const auto patch = pLandscape->findPatch(patchVect[i]); +// const auto pPop = patch->getPopn(pSpecies); +// if (pPop != 0) { +// popSizes[i] = pPop->sampleSize(); +// } // else popSizes[i] remain default init value 0, safe +// popWeights[i] = popSizes[i] - (popSizes[i] * popSizes[i] / totSize); // n_ic in Weir & Hill 2002 +// sumWeights += popWeights[i]; +// if (popSizes[i] > 0) nbPops++; +// +// // Fill the pairwise Fst matrix with default value 0 +// for (int j = 0; j < nPatches; j++) +// numeratorPairwiseFst[i][j] = 0; +// } +// +// nbExtantPops = nbPops; +// +// if (nbPops > 1) { +// // Calculate Fst numerators and denominators +// double p, pq, pBar, sqDist, num; +// for (int i = 0; i < nPatches; ++i) { +// if (popSizes[i] == 0) continue; +// const auto patch = pLandscape->findPatch(patchVect[i]); +// const auto pPop = patch->getPopn(pSpecies); +// +// for (int l = 0; l < nLoci; ++l) { +// for (int u = 0; u < nAlleles; ++u) { +// p = pPop->getAlleleFrequency(l, u); //p_liu +// pq = p * (1 - p); +// pBar = commNeutralCountTables[l].getFrequency(u); +// sqDist = p - pBar; //(p_liu - pbar_u)^2 +// sqDist *= sqDist; +// +// num = pq * popSizes[i] ; // eq. 8 Weir & Hill 2002 +// num /= popSizes[i] == 1 ? 1 : popSizes[i] - 1; // avoid division by zero +// numeratorPairwiseFst[i][i] += num; +// numeratorWeightedFst += num * popSizes[i]; // see equ. 9, Weir & Hill 2002 +// denominator += popSizes[i] * sqDist + popWeights[i] * pq; //common denominator +// +// } // end for allele +// } // end for locus +// } // end for pop +// +// // Diagonals +// double pairwiseFst; +// for (int i = 0; i < nPatches; ++i) { +// if (popSizes[i] == 0) continue; +// else if (denominator != 0) +// { +// pairwiseFst = 1 - (numeratorPairwiseFst[i][i] * sumWeights / denominator); +// pairwiseFstMatrix.set(i, i, pairwiseFst); +// } +// // else remain 0 +// } +// +// // Add allele frequencies to numerators +// double pi, pj; +// for (int l = 0; l < nLoci; ++l) +// for (int u = 0; u < nAlleles; ++u) +// for (int i = 0; i < nPatches - 1; ++i) { // nPatches-1 bc bottom row not filled +// if (popSizes[i] == 0) continue; +// const auto patch = pLandscape->findPatch(patchVect[i]); +// const auto pPopI = patch->getPopn(pSpecies); +// +// for (int j = i + 1; j < nPatches; ++j) { // fill only upper half of matrix +// if (popSizes[j] == 0) continue; +// const auto patch = pLandscape->findPatch(patchVect[j]); +// const auto pPopJ = patch->getPopn(pSpecies); +// +// pi = pPopI->getAlleleFrequency(l, u); +// pj = pPopJ->getAlleleFrequency(l, u); +// numeratorPairwiseFst[i][j] += pi * (1 - pj) + pj * (1 - pi); // equ. 7 of Weir & Hill 2002 +// } +// } +// +// // Final estimates of pairwise Fst (beta_ii' in eq. 7 in WC 2002) +// for (int i = 0; i < nPatches - 1; ++i) { +// if (popSizes[i] == 0) continue; // Fst for this pair remains NULL +// for (int j = i + 1; j < nPatches; ++j) { +// if (popSizes[j] == 0) continue; +// else if (denominator != 0) { +// pairwiseFst = 1 - (numeratorPairwiseFst[i][j] * sumWeights) / (2 * denominator); +// pairwiseFstMatrix.set(i, j, pairwiseFst); +// } +// // else remain 0 +// } +// } +// +// // Estimator of global Fst weighted by sample sizes (beta_W in eq. 9 in WH 2002) +// if (denominator != 0) { +// weightedFst = 1 - (numeratorWeightedFst * sumWeights) / (denominator * totSize); // beta_w in Eq. 9 in WH 2002 +// } +// else { +// weightedFst = 0.0; +// } +// } +// else { // zero or one pop, cannot calculate Fst +// // pairwiseFstMatrix keeps default values (0) +// weightedFst = 0.0; +// } +//} + diff --git a/NeutralStatsManager.h b/NeutralStatsManager.h new file mode 100644 index 0000000..dbc24c8 --- /dev/null +++ b/NeutralStatsManager.h @@ -0,0 +1,195 @@ +#ifndef NEUTRALSTATSH +#define NEUTRALSTATSH + +#include "Species.h" +#include "Landscape.h" +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + + +using namespace std; + +// Patch * patch matrix to store pairwise Fst calculations +/** Creates an array of doubles of size = rows*cols, taken from NEMO **/ +struct PatchMatrix +{ +public: + PatchMatrix(unsigned int nRows = 0, unsigned int nCols = 0) : + rows{ nRows }, + cols{ nCols }, + nbCells{nCols * nRows} { + value.resize(nbCells); + }; + + // Get value at specified position + double get(unsigned int i, unsigned int j) { + if (i >= cols || j >= rows) + throw runtime_error("PatchMatrix::get() out of range!\n"); + else return value[i * cols + j]; + } + + int getNbCells() { return nbCells; }; + + /** Sets element at row i and column j to value val **/ + void set(unsigned int i, unsigned int j, double val) { + if (i >= cols || j >= rows) + throw runtime_error("PatchMatrix::set() out of range!\n"); + else value[i * cols + j] = val; + } + + /** Assigns a value to all elements of the matrix. */ + void setAll(double val) + { + for (unsigned int i = 0; i < nbCells; ++i) value[i] = val; + } + +private: + unsigned int rows, cols, nbCells; + vector value; +}; + +// Counts of NEUTRAL allele occurrences in populations +// for neutral statistics calculations +struct NeutralCountsTable { + +public: + NeutralCountsTable(int maxNbNeutralAlleles) : alleleTallies(maxNbNeutralAlleles), alleleFrequencies(maxNbNeutralAlleles), alleleHeterozygoteTallies(maxNbNeutralAlleles) {}; + + void reset() { + fill(alleleTallies.begin(), alleleTallies.end(), 0); fill(alleleFrequencies.begin(), alleleFrequencies.end(), 0); + fill(alleleHeterozygoteTallies.begin(), alleleHeterozygoteTallies.end(), 0); + } + + // Getters + int getTally(int whichAllele) { return alleleTallies[whichAllele]; }; + double getFrequency(int whichAllele) { return alleleFrequencies[whichAllele]; }; + int getHeteroTally(int whichAllele) { + return alleleHeterozygoteTallies[whichAllele]; + }; + + // Setters / increments + void incrementTally(int whichAllele) { alleleTallies[whichAllele]++; }; + void incrementTallyBy(int count, int whichAllele) { this->alleleTallies[whichAllele] += count; } + void incrementHeteroTally(int whichAllele) { this->alleleHeterozygoteTallies[whichAllele]++; } + void setFrequencies(int sampleSize) { + for (int i = 0; i < alleleFrequencies.size(); i++) { + alleleFrequencies[i] = sampleSize > 0 ? static_cast(alleleTallies[i]) / sampleSize : 0.0; + } + }; + +private: + // Tallies, one for each possible allele (so absolute max size is 255) + vector alleleTallies; // number of occurrences of each allele in pop + vector alleleFrequencies; // frequency of each allele in pop + vector alleleHeterozygoteTallies; // nb of times each allele is found in a heterozygous pair +}; + + +// Handles calculations of neutral statistics +class NeutralStatsManager { + +public: + + NeutralStatsManager(const int& nbSampledPatches, const int nLoci); + + // Count alleles and their frequencies in all pops and community + void updateAllNeutralTables(Species* pSpecies, Landscape* pLandscape, set const& patchList); + void resetCommNeutralTables(); + + void calcAllelicDiversityMetrics(set const& patchList, const int nInds, Species* pSpecies, Landscape* pLandscape); + + // Heterozygosity calculations + void calculateHo(set const& patchList, const int totalNbSampledInds, const int nbrLoci, Species* pSpecies, Landscape* pLandscape); + void calculateHs(set const& patchList, const int nbrLoci, Species* pSpecies, Landscape* pLandscape); + void calculateHt(Species* pSpecies, Landscape* pLandscape, const int nLoci, const int nAlleles); + void calculatePerLocusHo(set const& patchList, const int totalNbSampledInds, const int nbrLoci, Species* pSpecies, Landscape* pLandscape); + + + + + // F-stats calculations + void calculateFstatWC(set const& patchList, const int nbSampledIndsInComm, const int nLoci, const int nAlleles, Species* pSpecies, Landscape* pLandscape, bool isPairwise); + + void calculatePairwiseFst(set const& patchList, const int nLoci, const int nAlleles, Species* pSpecies, Landscape* pLandscape); + + //void calcPairwiseWeightedFst(set const& patchList, const int nInds, const int nLoci, Species* pSpecies, Landscape* pLandscape); + + // Getters + int getNbPopulatedSampledPatches() const { return nbExtantPops; } + int getTotalNbSampledInds() const { return totalNbSampledInds; } + + double getMeanNbAllPerLocusPerPatch() const { return meanNbAllelesPerLocusPerPatch; } + double getMeanNbAllPerLocus() const { return meanNbAllelesPerLocus; } + double getMeanFixdAllelesPerPatch() const { return meanNbFixedLociPerPatch; } + double getTotalFixdAlleles() const { return meanFixedLoci; } + + double getHo() const { return ho; } + double getHs() const { return hs; } + double getHt() const { return ht; } + + double getPerLocusHo(int i) const { return perLocusHo[i]; } + + double getFstWC() const { return fst; } + double getFisWC() const { return fis; } + double getFitWC() const { return fit; } + + //double getWeightedFst() { return weightedFst; } + + double getPerLocusFst(int i) const { return perLocusFst[i]; } + double getPerLocusFis(int i) const { return perLocusFis[i]; } + double getPerLocusFit(int i) const { return perLocusFit[i]; } + + double getPairwiseFst(int i, int j) { return pairwiseFstMatrix.get(i, j); } + +private: + + int nbExtantPops, totalNbSampledInds; + vector commNeutralCountTables; // community-level tallies of allele counts and freqs + + double meanNbAllelesPerLocusPerPatch, meanNbAllelesPerLocus; + double meanNbFixedLociPerPatch, meanFixedLoci; + + double ho; // observed heterozygosity + double hs; // expected population-level heterozygosity + double ht; // expected community-level heterozygosity + + vector perLocusHo; // Per-locus observed heterozygosity + + // F-statistics + // Weir & Cockerham (1984) F-stat estimates. + double fst, fis, fit; + + // Weir & Hill (2002) F-stat estimates + double weightedFst; + + // Per-locus F-stats (Weir & Cockerham). + vector perLocusFst, perLocusFis, perLocusFit; + + // Pairwise Fst matrix + PatchMatrix pairwiseFstMatrix; +}; + +#endif + + + + diff --git a/NeutralTrait.cpp b/NeutralTrait.cpp new file mode 100644 index 0000000..dc7742a --- /dev/null +++ b/NeutralTrait.cpp @@ -0,0 +1,341 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + +#include "NeutralTrait.h" + +// ---------------------------------------------------------------------------------------- +// Initialisation constructor +// Called when initialising community +// Sets up initial values, and immutable attributes (distributions and parameters) +// that are defined at the species-level +// ---------------------------------------------------------------------------------------- +NeutralTrait::NeutralTrait(SpeciesTrait* P) +{ + pSpeciesTrait = P; + + DistributionType mutationDistribution = pSpeciesTrait->getMutationDistribution(); + map mutationParameters = pSpeciesTrait->getMutationParameters(); + + // Set default value to user-specified max + wildType = (int)mutationParameters.find(MAX)->second; + if (wildType > NeutralValUpperBound) + throw logic_error("max number of alleles cannot exceed " + to_string(NeutralValUpperBound) + ".\n"); + + _inherit_func_ptr = (pSpeciesTrait->getPloidy() == 1) ? &NeutralTrait::inheritHaploid : &NeutralTrait::inheritDiploid; //this could be changed if we wanted some alternative form of inheritance + + if (mutationDistribution == SSM) + _mutate_func_ptr = &NeutralTrait::mutate_SSM; + + if (mutationDistribution == KAM) + _mutate_func_ptr = &NeutralTrait::mutate_KAM; + + if (mutationDistribution != SSM && mutationDistribution != KAM) + throw logic_error("wrong mutation distribution for neutral markers, must be KAM or SSM \n"); + + if (mutationParameters.count(MAX) != 1) + throw logic_error("KAM or SSM mutation distribution parameter must contain max value (e.g. max= ), max cannot exceed 256 \n"); + + DistributionType initialDistribution = pSpeciesTrait->getInitialDistribution(); + map initialParameters = pSpeciesTrait->getInitialParameters(); + + if (mutationDistribution == SSM && initialDistribution != UNIFORM) + throw logic_error("If using SSM mutation model for neutral trait, must use uniform initial distribution.\n"); + + switch (initialDistribution) { + case UNIFORM: + { + if (initialParameters.count(MAX) != 1) + throw logic_error("initial distribution parameter must contain one max value if set to UNIFORM (e.g. max= ), max cannot exceed " + to_string(NeutralValUpperBound) + "\n"); + + float maxNeutralVal = initialParameters.find(MAX)->second; + if (maxNeutralVal > NeutralValUpperBound) { + throw logic_error("initial distribution parameter max cannot exceed " + to_string(NeutralValUpperBound) + ", resetting to " + to_string(NeutralValUpperBound) + "\n"); + maxNeutralVal = NeutralValUpperBound; //reserve 255 for wildtype + } + initialiseUniform(maxNeutralVal); + break; + } + default: + { + throw logic_error("wrong parameter value for parameter \"initialisation of neutral trait\", must be left uniform \n"); + break; //should return false + } + } +} + +// ---------------------------------------------------------------------------------------- +// Inheritance constructor +// Copies immutable features from a parent trait +// Only called via clone() +// ---------------------------------------------------------------------------------------- +NeutralTrait::NeutralTrait(const NeutralTrait& T) : + pSpeciesTrait(T.pSpeciesTrait), _mutate_func_ptr(T._mutate_func_ptr), _inherit_func_ptr(T._inherit_func_ptr) { +} + +// ---------------------------------------------------------------------------------------- +// mutate options +// ---------------------------------------------------------------------------------------- + +// ---------------------------------------------------------------------------------------- +// Draw and apply mutations according to a KAM process +// +// Mutations drawn only for existing positions, +// that is no new genes are created during simulation +// KAM = randomly drawn value in 0-MAX, differs from previous value +// ---------------------------------------------------------------------------------------- +void NeutralTrait::mutate_KAM() +{ + const int positionsSize = pSpeciesTrait->getPositionsSize(); + const auto& genePositions = pSpeciesTrait->getGenePositions(); + const short ploidy = pSpeciesTrait->getPloidy(); + const float mutationRate = pSpeciesTrait->getMutationRate(); + auto rng = pRandom->getRNG(); + unsigned char mut; + + map mutationParameters = pSpeciesTrait->getMutationParameters(); + + int maxNeutralVal = (int)mutationParameters.find(MAX)->second; + if (maxNeutralVal > NeutralValUpperBound) maxNeutralVal = NeutralValUpperBound; //reserve max value for wildtype + + for (int whichChromosome = 0; whichChromosome < ploidy; whichChromosome++) { + + unsigned int NbMut = pRandom->Binomial(positionsSize, mutationRate); + if (NbMut > 0) { + vector mutationPositions; + sample(genePositions.begin(), genePositions.end(), std::back_inserter(mutationPositions), + NbMut, rng); // without replacement + + for (int m : mutationPositions) { + mut = (unsigned char)pRandom->IRandom(0, maxNeutralVal); // draw new mutation, could draw wildtype + + auto it = genes.find(m); + if (it == genes.end()) + throw runtime_error("Locus selected for mutation doesn't exist."); + auto currentChar = it->second[whichChromosome]; // current allele + if (maxNeutralVal > 0) { // dodge the infinite loop + do { + mut = (unsigned char)pRandom->IRandom(0, maxNeutralVal); + } while (mut == currentChar); // new allele value is different + } + else mut = 0; + it->second[whichChromosome] = mut; //overwrite with new value + } + } + } +} + + +// ---------------------------------------------------------------------------------------- +// Draw and apply single-step mutations (SSM) +// +// Mutations drawn only for existing positions, +// that is no new genes are created during simulation +// Increment previous value by 1 or -1, +// unless already 0 (then always +1) or MAX (then always -1) +// ---------------------------------------------------------------------------------------- +void NeutralTrait::mutate_SSM() +{ + const int positionsSize = pSpeciesTrait->getPositionsSize(); + const auto& genePositions = pSpeciesTrait->getGenePositions(); + const short ploidy = pSpeciesTrait->getPloidy(); + const float mutationRate = pSpeciesTrait->getMutationRate(); + auto rng = pRandom->getRNG(); + + map mutationParameters = pSpeciesTrait->getMutationParameters(); + + int maxNeutralVal = (int)mutationParameters.find(MAX)->second; + if (maxNeutralVal > NeutralValUpperBound) maxNeutralVal = NeutralValUpperBound; //reserved max value for wildtype + + for (int whichChromosome = 0; whichChromosome < ploidy; whichChromosome++) { + + unsigned int NbMut = pRandom->Binomial(positionsSize, mutationRate); + if (NbMut > 0) { + vector mutationPositions; + sample(genePositions.begin(), genePositions.end(), std::back_inserter(mutationPositions), + NbMut, rng); + + for (int m : mutationPositions) { + int mutateUp = pRandom->Bernoulli(0.5); + auto it = genes.find(m); + if (it == genes.end()) + throw runtime_error("Locus selected for mutation doesn't exist."); + auto currentAllele = it->second[whichChromosome]; + if (mutateUp == 1 && currentAllele < maxNeutralVal) + it->second[whichChromosome] += 1; // one step up + else if (currentAllele > 0) // step down or already max + it->second[whichChromosome] -= 1; // one step down + else // current allele is 0, step up + it->second[whichChromosome] += 1; + } + } + } +} + +// ---------------------------------------------------------------------------------------- +// Wrapper to inheritance function +// ---------------------------------------------------------------------------------------- +void NeutralTrait::inheritGenes(const bool& fromMother, QuantitativeTrait* parent, set const& recomPositions, int startingChromosome) +{ + auto parentCast = dynamic_cast (parent); // must convert QuantitativeTrait to NeutralTrait + const auto& parent_seq = parentCast->getGenes(); + (this->*_inherit_func_ptr) (fromMother, parent_seq, recomPositions, startingChromosome); +} + +// ---------------------------------------------------------------------------------------- +// Inheritance for diploid, sexual species +// Called once for each parent. Given a list of recombinant sites, +// populates offspring genes with appropriate parent alleles +// Assumes mother genes are inherited first +// ---------------------------------------------------------------------------------------- +void NeutralTrait::inheritDiploid(const bool& fromMother, map> const& parentGenes, set const& recomPositions, int parentChromosome) { + + const int lastPosition = parentGenes.rbegin()->first; + auto recomIt = recomPositions.lower_bound(parentGenes.begin()->first); + // If no recombination sites, only breakpoint is last position + // i.e., no recombination occurs + int nextBreakpoint = recomIt == recomPositions.end() ? lastPosition : *recomIt; + + // Is the first parent gene position already recombinant? + auto distance = std::distance(recomPositions.begin(), recomIt); + if (distance % 2 != 0) + parentChromosome = 1 - parentChromosome; //switch chromosome + + for (auto const& [locus, allelePair] : parentGenes) { + + // Switch chromosome if locus is past recombination site + while (locus > nextBreakpoint) { + parentChromosome = 1 - parentChromosome; + std::advance(recomIt, 1); // go to next recombination site + nextBreakpoint = recomIt == recomPositions.end() ? lastPosition : *recomIt; + } + + if (locus <= nextBreakpoint) { + unsigned char parentAllele = allelePair[parentChromosome]; + auto it = genes.find(locus); + if (it == genes.end()) { + // locus does not exist yet, create and initialise it + if (!fromMother) throw runtime_error("Father-inherited locus does not exist."); + vector newAllelePair(2, wildType); + newAllelePair[sex_t::FEM] = parentAllele; + genes.insert(make_pair(locus, newAllelePair)); + } + else { // father, locus already exists + if (fromMother) throw runtime_error("Mother-inherited locus already exists."); + it->second[sex_t::MAL] = parentAllele; + } + } + } +} + +// ---------------------------------------------------------------------------------------- +// Inheritance for haploid, asexual species +// Simply pass down parent genes +// Arguments are still needed to match overloaded function in base class +// ---------------------------------------------------------------------------------------- +void NeutralTrait::inheritHaploid(const bool& fromMother, map> const& parentGenes, set const& recomPositions, int parentChromosome) +{ + genes = parentGenes; +} + +// ---------------------------------------------------------------------------------------- +// Initialise neutral loci +// ---------------------------------------------------------------------------------------- +void NeutralTrait::initialiseUniform(int maxAlleleVal) +{ + const auto& genePositions = pSpeciesTrait->getGenePositions(); + const auto& initPositions = pSpeciesTrait->getInitPositions(); + short ploidy = pSpeciesTrait->getPloidy(); + + for (auto position : genePositions) { + vector allelePair; + + for (int i = 0; i < ploidy; i++) { + unsigned char alleleVal = char(0); + if (initPositions.contains(position)) { + // allele values span 0 - max inclusive, max is wildtype + alleleVal = (unsigned char)pRandom->IRandom(0, maxAlleleVal); + } + allelePair.emplace_back(alleleVal); + } + genes.insert(make_pair(position, allelePair)); + } +} + + +// ---------------------------------------------------------------------------------------- +// Check if particular loci is heterozygote +// ---------------------------------------------------------------------------------------- +bool NeutralTrait::isHeterozygoteAtLocus(int locus) const { + // assumes diploidy + auto it = genes.find(locus); + if (it == genes.end()) + throw runtime_error("Neutral gene queried for heterozygosity does not exist."); + else + return(it->second[0] != it->second[1]); +} + +// ---------------------------------------------------------------------------------------- +// Count heterozygote loci in genome +// ---------------------------------------------------------------------------------------- +int NeutralTrait::countHeterozygoteLoci() const { + // assumes diploidy + int count = 0; + for (auto const& [locus, allelePair] : genes) { + count += (allelePair[0] != allelePair[1]); + } + return count; +} + +// ---------------------------------------------------------------------------------------- +// Get allele value at loci +// ---------------------------------------------------------------------------------------- +float NeutralTrait::getAlleleValueAtLocus(short whichChromosome, int position) const { + + auto it = genes.find(position); + if (it == genes.end()) //no mutations there + throw runtime_error("The neutral locus queried for its allele value does not exist."); + return it->second[whichChromosome]; +} + +#ifdef UNIT_TESTS // Testing only + +// Create a default set of neutral alleles for testing +// +// Shorthand function to manually set genotypes for neutral +// traits, instead of having to manipulate mutations. +map> createTestNeutralGenotype( + const int genomeSz, const bool isDiploid, + const unsigned char valAlleleA, + const unsigned char valAlleleB +) { + vector gene(isDiploid ? 2 : 1); + gene[0] = valAlleleA; + if (isDiploid) gene[1] = valAlleleB; + + map> genotype; + for (int i = 0; i < genomeSz; i++) { + genotype.emplace(i, gene); + } + return genotype; +} + +#endif // UNIT_TESTS diff --git a/NeutralTrait.h b/NeutralTrait.h new file mode 100644 index 0000000..aa87159 --- /dev/null +++ b/NeutralTrait.h @@ -0,0 +1,116 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + +#ifndef NeutralTRAITH +#define NeutralTRAITH + +#include +#include +#include +#include + +#include "QuantitativeTrait.h" + +using namespace std; + +// Neutral traits +// +// Not expressed and are only used to compute neutral statistics +// e.g. the Fst. +// To save on mem usage, allele values are represented by character types, +// taking a value between 0 and a user-specified max >= 255 +class NeutralTrait : public QuantitativeTrait { + +public: + + // Initialisation constructor, set initial values and immutable features + NeutralTrait(SpeciesTrait* P); + + // Inheritance constructor, copies pointers to immutable features when cloning from parent + NeutralTrait(const NeutralTrait& T); + + // Make a shallow copy to pass to offspring trait + // Return new pointer to new trait created by inheritance c'tor + // This avoids copying shared attributes: distributions and parameters + virtual unique_ptr clone() const override { return std::make_unique(*this); } + + virtual ~NeutralTrait() { } + + // Getters + virtual int getNLoci() const override { return pSpeciesTrait->getPositionsSize(); } + float getMutationRate() const override { return pSpeciesTrait->getMutationRate(); } + bool isInherited() const override { return pSpeciesTrait->isInherited(); } + map>& getGenes() { return genes; } //returning reference, reciever must be const + + virtual void mutate() override { (this->*_mutate_func_ptr) (); } + virtual void inheritGenes(const bool& fromMother, QuantitativeTrait* parent, set const& recomPositions, int startingChromosome) override; + virtual float express() { + throw runtime_error("Neutral trait shouldn't be expressed."); + return -9999; + } + + virtual float getAlleleValueAtLocus(short chromosome, int position) const override; + virtual float getDomCoefAtLocus(short chromosome, int position) const override { + return 0.0; + } + + int countHeterozygoteLoci() const; + bool isHeterozygoteAtLocus(int locus) const; + +private: + + inline static int wildType; // default allele value, value set at construction + const int NeutralValUpperBound = UCHAR_MAX; // alleles are char, can take value 0-255 + + // > + map> genes; + + // Initialisation + void initialiseUniform(int max); + + // Immutable features, set at initialisation + // and passed down to every subsequent trait copy + //// Species-level trait attributes, invariant across individuals + SpeciesTrait* pSpeciesTrait; + //// Species-level trait functions + void (NeutralTrait::* _mutate_func_ptr) (void); + void (NeutralTrait::* _inherit_func_ptr) (const bool& fromMother, map> const& parent, set const& recomPositions, int parentChromosome); + + // Possible values for immutable functions + //// Inheritance + void inheritDiploid(const bool& fromMother, map> const&, set const& recomPositions, int parentChromosome); + void inheritHaploid(const bool& fromMother, map> const& parentMutations, set const& recomPositions, int parentChromosome); + //// Mutation + void mutate_KAM(); + void mutate_SSM(); // single-step mutations + +}; + +#ifdef UNIT_TESTS // for testing purposes only +map> createTestNeutralGenotype( + const int genomeSz, const bool isDiploid, + const unsigned char valAlleleA, + const unsigned char valAlleleB = char(0) // if haploid +); +#endif + +#endif // NeutralTraitH diff --git a/Parameters.cpp b/Parameters.cpp index 5cfb3d7..ff36d9d 100644 --- a/Parameters.cpp +++ b/Parameters.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -128,7 +128,7 @@ paramInit::paramInit(void) { minSeedX = 0; maxSeedX = 99999999; minSeedY = 0; maxSeedY = 99999999; nSeedPatches = 1; nSpDistPatches = 1; indsFile = "NULL"; - for (int i = 0; i < NSTAGES; i++) { + for (int i = 0; i < gMaxNbStages; i++) { initProp[i] = 0.0; } } @@ -176,12 +176,12 @@ initParams paramInit::getInit(void) { } void paramInit::setProp(short stg, float p) { - if (stg >= 0 && stg < NSTAGES && p >= 0.0 && p <= 1.0) initProp[stg] = p; + if (stg >= 0 && stg < gMaxNbStages && p >= 0.0 && p <= 1.0) initProp[stg] = p; } float paramInit::getProp(short stg) { float p = 0.0; - if (stg >= 0 && stg < NSTAGES) p = initProp[stg]; + if (stg >= 0 && stg < gMaxNbStages) p = initProp[stg]; return p; } @@ -210,30 +210,27 @@ int paramInit::numInitInds(void) { return (int)initinds.size(); } // Simulation parameters -paramSim::paramSim(void) { +paramSim::paramSim(const string& pathToProjDir) : + dir{pathToProjDir} +{ simulation = 0; reps = years = 1; outIntRange = 1; - outStartPop = outStartInd = outStartGenetic = 0; + outStartPop = outStartInd = 0; outStartTraitCell = outStartTraitRow = outStartConn = 0; - outIntOcc = outIntPop = outIntInd = outIntGenetic = 10; + outputGlobalFstStart = outputPairwiseFstStart = 0; + outIntOcc = outIntPop = outIntInd = outputGlobalFstInterval = outputPairwiseFstInterval= 10; outIntTraitCell = outIntTraitRow = outIntConn = 10; - mapInt = traitInt = 10; - slowFactor = 1; + traitInt = 10; batchMode = absorbing = false; outRange = outOccup = outPop = outInds = false; - outGenetics = outGenXtab = false; outGenType = 0; outTraitsCells = outTraitsRows = outConnect = false; - saveMaps = false; saveTraitMaps = false; + outputGenes = outputGlobalFst = outPairwiseFst = false; saveVisits = false; #if RS_RCPP outStartPaths = 0; outIntPaths = 0; - outPaths = false; ReturnPopRaster = false; CreatePopFile = true; + outPaths = false; ReturnPopMatrix = false; ReturnPopDataFrame = false; CreatePopFile = true; #endif - drawLoaded = false; - viewLand = false; viewPatch = false; viewGrad = false; viewCosts = false; - viewPop = false; viewTraits = false; viewPaths = false; viewGraph = false; - dir = ' '; } paramSim::~paramSim(void) { } @@ -243,21 +240,14 @@ void paramSim::setSim(simParams s) { if (s.simulation >= 0) simulation = s.simulation; if (s.reps >= 1) reps = s.reps; if (s.years >= 1) years = s.years; - if (s.mapInt >= 1) mapInt = s.mapInt; if (s.traitInt >= 1) traitInt = s.traitInt; batchMode = s.batchMode; absorbing = s.absorbing; outRange = s.outRange; outOccup = s.outOccup; outPop = s.outPop; outInds = s.outInds; - outGenetics = s.outGenetics; - if (s.outGenType >= 0 && s.outGenType <= 2) { - outGenType = s.outGenType; - } - outGenXtab = s.outGenXtab; outTraitsCells = s.outTraitsCells; outTraitsRows = s.outTraitsRows; outConnect = s.outConnect; if (s.outStartPop >= 0) outStartPop = s.outStartPop; if (s.outStartInd >= 0) outStartInd = s.outStartInd; - if (s.outStartGenetic >= 0) outStartGenetic = s.outStartGenetic; if (s.outStartTraitCell >= 0) outStartTraitCell = s.outStartTraitCell; if (s.outStartTraitRow >= 0) outStartTraitRow = s.outStartTraitRow; if (s.outStartConn >= 0) outStartConn = s.outStartConn; @@ -265,78 +255,86 @@ void paramSim::setSim(simParams s) { if (s.outIntOcc >= 1) outIntOcc = s.outIntOcc; if (s.outIntPop >= 1) outIntPop = s.outIntPop; if (s.outIntInd >= 1) outIntInd = s.outIntInd; - if (s.outIntGenetic >= 1) outIntGenetic = s.outIntGenetic; if (s.outIntTraitCell >= 1) outIntTraitCell = s.outIntTraitCell; if (s.outIntTraitRow >= 1) outIntTraitRow = s.outIntTraitRow; if (s.outIntConn >= 1) outIntConn = s.outIntConn; - saveMaps = s.saveMaps; saveTraitMaps = s.saveTraitMaps; saveVisits = s.saveVisits; #if RS_RCPP outStartPaths = s.outStartPaths; outIntPaths = s.outIntPaths; outPaths = s.outPaths; - ReturnPopRaster = s.ReturnPopRaster; + ReturnPopMatrix = s.ReturnPopMatrix; + ReturnPopDataFrame = s.ReturnPopDataFrame; + ReturnStages = s.ReturnStages; CreatePopFile = s.CreatePopFile; #endif - drawLoaded = s.drawLoaded; + fixReplicateSeed = s.fixReplicateSeed; +} + +void paramSim::setGeneticSim(string patchSamplingOption, bool outputGenes, int outputGenesStart, int outputGenesInterval, bool outPairwiseFst, + int outputGlobalFst, int outputStartGlobalFst, int outputGlobalFstInterval, int outputStartPairwiseFst, int outputPairwiseFstIntervals, bool outputPerLocusFst) { + this->patchSamplingOption = patchSamplingOption; + this->outputGenes = outputGenes; + this->outputGenesStart = outputGenesStart; + this->outputGenesInterval = outputGenesInterval; + this->outputGlobalFst = outputGlobalFst; + this->outputGlobalFstStart = outputStartGlobalFst; + this->outputGlobalFstInterval = outputGlobalFstInterval; + this->outPairwiseFst = outPairwiseFst; + this->outputPairwiseFstStart = outputStartPairwiseFst; + this->outputPairwiseFstInterval = outputPairwiseFstIntervals; + this->outputPerLocusFst = outputPerLocusFst; } -simParams paramSim::getSim(void) { +simParams paramSim::getSim() { simParams s; s.batchNum = batchNum; s.simulation = simulation; s.reps = reps; s.years = years; s.outRange = outRange; s.outOccup = outOccup; s.outPop = outPop; s.outInds = outInds; - s.outGenetics = outGenetics; s.outGenType = outGenType; s.outGenXtab = outGenXtab; s.outTraitsCells = outTraitsCells; s.outTraitsRows = outTraitsRows; s.outConnect = outConnect; - s.outStartPop = outStartPop; s.outStartInd = outStartInd; s.outStartGenetic = outStartGenetic; + s.outStartPop = outStartPop; s.outStartInd = outStartInd; s.outStartTraitCell = outStartTraitCell; s.outStartTraitRow = outStartTraitRow; s.outStartConn = outStartConn; s.outIntRange = outIntRange; s.outIntOcc = outIntOcc; s.outIntPop = outIntPop; - s.outIntInd = outIntInd; s.outIntGenetic = outIntGenetic; + s.outIntInd = outIntInd; s.outIntTraitCell = outIntTraitCell; s.outIntTraitRow = outIntTraitRow; s.outIntConn = outIntConn; s.batchMode = batchMode; s.absorbing = absorbing; - s.saveMaps = saveMaps; s.saveTraitMaps = saveTraitMaps; - s.saveVisits = saveVisits; - s.mapInt = mapInt; s.traitInt = traitInt; + s.traitInt = traitInt; #if RS_RCPP + s.saveVisits = saveVisits; s.outStartPaths = outStartPaths; s.outIntPaths = outIntPaths; s.outPaths = outPaths; - s.ReturnPopRaster = ReturnPopRaster; + s.ReturnPopMatrix = ReturnPopMatrix; + s.ReturnPopDataFrame = ReturnPopDataFrame; + s.ReturnStages = ReturnStages; s.CreatePopFile = CreatePopFile; #endif - s.drawLoaded = drawLoaded; + s.patchSamplingOption = patchSamplingOption; + + s.outputGenes = outputGenes; + s.outputGenesStart = outputGenesStart; + s.outputGenesInterval = outputGenesInterval; + + s.outputGlobalFst = outputGlobalFst; + s.outputGlobalFstStart = outputGlobalFstStart; + s.outputGlobalFstInterval = outputGlobalFstInterval; + + s.outPairwiseFst = outPairwiseFst; + s.outputPairwiseFstStart = outputPairwiseFstStart; + s.outputPairwiseFstInterval = outputPairwiseFstInterval; + + s.outputPerLocusFst = outputPerLocusFst; + return s; } int paramSim::getSimNum(void) { return simulation; } -void paramSim::setViews(simView v) { - viewLand = v.viewLand; viewPatch = v.viewPatch; - viewGrad = v.viewGrad; viewCosts = v.viewCosts; - viewPop = v.viewPop; viewTraits = v.viewTraits; - viewPaths = v.viewPaths; viewGraph = v.viewGraph; - if (v.slowFactor > 0) slowFactor = v.slowFactor; -} - -simView paramSim::getViews(void) { - simView v; - v.viewLand = viewLand; v.viewPatch = viewPatch; - v.viewGrad = viewGrad; v.viewCosts = viewCosts; - v.viewPop = viewPop; v.viewTraits = viewTraits; - v.viewPaths = viewPaths; v.viewGraph = viewGraph; - v.slowFactor = slowFactor; - return v; -} - -void paramSim::setDir(string s) { - dir = s; -} - // return directory name depending on option specified string paramSim::getDir(int option) { string s; @@ -371,12 +369,103 @@ string paramSim::getDir(int option) { return s; } +string to_string(const TraitType& tr) { + switch (tr) + { + case NEUTRAL: return "NEUTRAL"; + case GENETIC_LOAD: return "GENETIC_LOAD"; + case GENETIC_LOAD1: return "GENETIC_LOAD1"; + case GENETIC_LOAD2: return "GENETIC_LOAD2"; + case GENETIC_LOAD3: return "GENETIC_LOAD3"; + case GENETIC_LOAD4: return "GENETIC_LOAD4"; + case GENETIC_LOAD5: return "GENETIC_LOAD5"; + + case E_D0: return "E_D0"; + case E_D0_M: return "E_D0_M"; + case E_D0_F: return "E_D0_F"; + case E_ALPHA: return "E_ALPHA"; + case E_ALPHA_M: return "E_ALPHA_M"; + case E_ALPHA_F: return "E_ALPHA_F"; + case E_BETA: return "E_BETA"; + case E_BETA_M: return "E_BETA_M"; + case E_BETA_F: return "E_BETA_F"; + + case S_S0: return "S_S0"; + case S_S0_M: return "S_S0_M"; + case S_S0_F: return "S_S0_F"; + case S_ALPHA: return "S_ALPHA"; + case S_ALPHA_M: return "S_ALPHA_M"; + case S_ALPHA_F: return "S_ALPHA_F"; + case S_BETA: return "S_BETA"; + case S_BETA_M: return "S_BETA_M"; + case S_BETA_F: return "S_BETA_F"; + + case CRW_STEPLENGTH: return "CRW_STEPLENGTH"; + case CRW_STEPCORRELATION: return "CRW_STEPCORRELATION"; + case KERNEL_MEANDIST_1: return "KERNEL_MEANDIST_1"; + case KERNEL_MEANDIST_2: return "KERNEL_MEANDIST_2"; + case KERNEL_MEANDIST_1_F: return "KERNEL_MEANDIST_1_F"; + case KERNEL_MEANDIST_2_F: return "KERNEL_MEANDIST_2_F"; + case KERNEL_MEANDIST_1_M: return "KERNEL_MEANDIST_1_M"; + case KERNEL_MEANDIST_2_M: return "KERNEL_MEANDIST_2_M"; + case KERNEL_PROBABILITY: return "KERNEL_PROBABILITY"; + case KERNEL_PROBABILITY_F: return "KERNEL_PROBABILITY_F"; + case KERNEL_PROBABILITY_M: return "KERNEL_PROBABILITY_M"; + + case SMS_DP: return "SMS_DP"; + case SMS_GB: return "SMS_GB"; + case SMS_ALPHADB: return "SMS_ALPHADB"; + case SMS_BETADB: return "SMS_BETADB"; + case INVALID_TRAIT: return "INVALID_TRAIT"; + default: return ""; + } +} + +string to_string(const GenParamType& param) { + switch (param) + { + case MEAN: return "MEAN"; + case SD: return "SD"; + case MIN: return "MIN"; + case MAX: return "MAX"; + case SHAPE: return "SHAPE"; + case SCALE: return "SCALE"; + case INVALID: return "INVALID"; + default: return ""; + } +} + +string to_string(const DistributionType& dist) { + switch (dist) + { + case UNIFORM: return "UNIFORM"; + case NORMAL: return "NORMAL"; + case GAMMA: return "GAMMA"; + case NEGEXP: return "NEGEXP"; + case SCALED: return "SCALED"; + case KAM: return "KAM"; + case SSM: return "SSM"; + case NONE: return "NONE"; + default: return ""; + } +} + +string to_string(const ExpressionType& expr) { + switch (expr) + { + case AVERAGE: return "AVERAGE"; + case ADDITIVE: return "ADDITIVE"; + case NOTEXPR: return "NOTEXPR"; + case MULTIPLICATIVE: return "MULTIPLICATIVE"; + default: return ""; + } +} + #if RS_RCPP -bool paramSim::getReturnPopRaster(void) { return ReturnPopRaster; } +bool paramSim::getReturnPopMatrix(void) { return ReturnPopMatrix; } +bool paramSim::getReturnPopDataFrame(void) { return ReturnPopDataFrame; } bool paramSim::getCreatePopFile(void) { return CreatePopFile; } #endif //--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- diff --git a/Parameters.h b/Parameters.h index 9cc51d2..f44929e 100644 --- a/Parameters.h +++ b/Parameters.h @@ -1,25 +1,25 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - + + /*------------------------------------------------------------------------------ RangeShifter v2.0 Parameters @@ -34,9 +34,9 @@ paramStoch - Environmental stochasticity parameters Also declares some structures and functions used throughout the program. For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. + Bocedi G., Palmer S.C.F., Pe’er G., Heikkinen R.K., Matsinos Y.G., Watts K. and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. + eco-evolutionary dynamics and species’ responses to environmental changes. Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 Authors: Greta Bocedi & Steve Palmer, University of Aberdeen @@ -48,43 +48,37 @@ Last updated: 25 June 2021 by Steve Palmer #ifndef ParametersH #define ParametersH -//#if LINUX_CLUSTER -//#include -//#else #include -//#endif #include #include -//#include #include #include #include +#include +#include using namespace std; #include "RSrandom.h" -#define NSTAGES 10 // maximum number of stages permitted -#define NSEXES 2 // maximum number of sexes permitted -#define PARAMDEBUG 0 -#define NTRAITS 18 // maximum number of variable traits which can be displayed - // in GUI (VCL version) -#define NSD 3.0 // no. of s.d. to use to control range for displaying traits +constexpr int gNoDataCost = 100000; // cost to use in place of nodata value for SMS; +constexpr int gAbsorbingNoDataCost = 100; // cost to use in place of nodata value for SMS; +// when boundaries are absorbing +constexpr int gMaxNbStages = 10; // maximum number of stages permitted +constexpr int gMaxNbSexes = 2; // maximum number of sexes permitted +constexpr int gMaxNbLayers = 3*gMaxNbSexes*gMaxNbStages; // maximum number of demographic scaling layers permitted #if RS_RCPP typedef intptr_t intptr; #else -#if RSWIN64 typedef unsigned long long intptr; -#else -typedef unsigned int intptr; -#endif -#endif +#endif // RS_RCPP #if RS_RCPP #ifndef R_EXT_CONSTANTS_H_ // the R headers define PI as a macro, so that the 'else' line results in an error #define M_2PI 6.283185307179586 const double PI = 3.141592653589793238462643383279502884197169399375; #endif +#include #else #define M_2PI 6.283185307179586 const double PI = 3.141592654; @@ -95,26 +89,62 @@ const double SQRT2 = std::sqrt(double(2.0)); // more efficient than calculating //--------------------------------------------------------------------------- // Common declarations - struct locn { int x; int y; }; -struct rgb { // colour scheme for drawing maps - int r,g,b; + +//-------------------------------------------------------------------------- + +/** Trait types **/ + +enum TraitType { + NEUTRAL, + GENETIC_LOAD, GENETIC_LOAD1, GENETIC_LOAD2, GENETIC_LOAD3, GENETIC_LOAD4, GENETIC_LOAD5, + + E_D0, E_ALPHA, E_BETA, + S_S0, S_ALPHA, S_BETA, + + E_D0_F, E_ALPHA_F, E_BETA_F, + S_S0_F, S_ALPHA_F, S_BETA_F, + + E_D0_M, E_ALPHA_M, E_BETA_M, + S_S0_M, S_ALPHA_M, S_BETA_M, + + CRW_STEPLENGTH, CRW_STEPCORRELATION, + + KERNEL_MEANDIST_1, KERNEL_MEANDIST_2, KERNEL_PROBABILITY, + KERNEL_MEANDIST_1_F, KERNEL_MEANDIST_2_F, KERNEL_PROBABILITY_F, + KERNEL_MEANDIST_1_M, KERNEL_MEANDIST_2_M, KERNEL_PROBABILITY_M, + + SMS_DP, SMS_GB, SMS_ALPHADB, SMS_BETADB, + + INVALID_TRAIT // error }; -const string Int2Str(const int); -#if RS_RCPP -const string Int2Str(const int, unsigned int); -#endif -const string Float2Str(const float); -const string Double2Str(const double); -const rgb draw_wheel(int); +enum GenParamType { MEAN, SD, MIN, MAX, SHAPE, SCALE, INVALID }; +enum DistributionType { UNIFORM, NORMAL, GAMMA, NEGEXP, SCALED, KAM, SSM, NONE }; +enum ExpressionType { AVERAGE, ADDITIVE, NOTEXPR, MULTIPLICATIVE }; + +string to_string(const TraitType& tr); + +string to_string(const GenParamType& param); + +string to_string(const DistributionType& dist); + +string to_string(const ExpressionType& expr); + +/** Param's types **/ +typedef enum { KERNEL, SMS, CRW} movement_t; + +//sex types +typedef enum { + FEM = 0, MAL = 1, + NA, // not applicable. e.g. for NEUTRAL or genetic load trait + INVALID_SEX // error +} sex_t; //--------------------------------------------------------------------------- // Environmental gradient parameters -// SHOULD THIS BE PART OF LANDSCAPE OBJECT OR A SEPARATE OBJECT????????????? - struct envGradParams { bool gradient; bool shifting; int gradType; float grad_inc; float opt_y; float factor; float extProbOpt; @@ -167,7 +197,7 @@ class paramGrad { struct envStochParams { bool stoch; bool local; bool inK; bool localExt; - float ac; float std; + float ac; float std; float locExtProb; }; @@ -185,7 +215,7 @@ class paramStoch { bool local; // applied locally (if not, application is global) bool inK; // in carrying capacity (if not, in growth rate) bool localExt; // local extinction applied - float ac; // temporal autocorrelation coefficient + float ac; // temporal autocorrelation coefficient float std; // amplitude of fluctuations: sampled from N(0,std) float locExtProb; // local extinction probability }; @@ -205,7 +235,8 @@ struct initParams { }; struct initInd { - int year,patchID,x,y; short species,sex,age,stage; + int year, patchID, x, y; + short species, sex, age, stage; }; class paramInit { @@ -260,7 +291,7 @@ class paramInit { int nSeedPatches; // no. of cells/patches to initialise int nSpDistPatches; // no. of species distribution cells to initialise string indsFile; // no. of species distribution cells to initialise - float initProp[NSTAGES]; // initial stage proportions (structured population only) + float initProp[gMaxNbStages]; // initial stage proportions (structured population only) vector initinds; // individuals to be initialised @@ -272,25 +303,45 @@ class paramInit { struct simParams { int batchNum; - int simulation; int reps; int years; -// int outStartRange; -// int outStartOcc; - int outStartPop; int outStartInd; int outStartGenetic; - int outStartTraitCell; int outStartTraitRow; int outStartConn; - int outIntRange; int outIntOcc; int outIntPop; int outIntInd; int outIntGenetic; - int outIntTraitCell; int outIntTraitRow; int outIntConn; - int mapInt; int traitInt; - bool batchMode; bool absorbing; - bool outRange; bool outOccup; bool outPop; bool outInds; - bool outGenetics; short outGenType; bool outGenXtab; - bool outTraitsCells; bool outTraitsRows; bool outConnect; - bool saveMaps; - bool drawLoaded; bool saveTraitMaps; + int simulation; + int reps; + int years; + int outStartPop; + int outStartInd; + int outIntInd; + int outStartTraitCell; + int outStartTraitRow; + int outStartConn; + int outIntRange; + int outIntOcc; + int outIntPop; bool saveVisits; + int outIntTraitCell; + int outIntTraitRow; + int outIntConn; + int traitInt; + bool batchMode; + bool absorbing; + bool outRange; + bool outOccup; + bool outPop; + bool outInds; + bool outTraitsCells; + bool outTraitsRows; + bool outConnect; #if RS_RCPP int outStartPaths; int outIntPaths; - bool outPaths; bool ReturnPopRaster; bool CreatePopFile; + bool outPaths; bool ReturnPopMatrix; bool ReturnPopDataFrame; bool CreatePopFile; + Rcpp::LogicalVector ReturnStages; #endif + bool fixReplicateSeed; + string patchSamplingOption; + bool outputGenes; + bool outputGlobalFst, outPairwiseFst; + bool outputPerLocusFst; + int outputGenesStart, outputGenesInterval; + int outputGlobalFstStart, outputGlobalFstInterval; + int outputPairwiseFstStart, outputPairwiseFstInterval; }; struct simView { @@ -302,85 +353,77 @@ struct simView { class paramSim { public: - paramSim(void); + paramSim(const string& pathToProjDir = ""); ~paramSim(void); void setSim(simParams); + void setGeneticSim(string patchSamplingOption, bool outputGenes, int outputGenesStart, int outputGenesInterval, bool outPairwiseFst, + int outputGlobalFst, int outputStartGlobalFst, int outputGlobalFstInterval, int outputStartPairwiseFst, int outputPairwiseFstIntervals, bool outputPerLocusFst); simParams getSim(void); int getSimNum(void); - void setViews(simView); - simView getViews(void); - void setDir(string); string getDir(int); + void setBatchNum(const int& batchNb) { + batchNum = batchNb; + batchMode = true; + } #if RS_RCPP - bool getReturnPopRaster(void); + bool getReturnPopDataFrame(void); + bool getReturnPopMatrix(void); bool getCreatePopFile(void); #endif private: - int batchNum; // batch number - int simulation; // simulation no. - int reps; // no. of replicates - int years; // no. of years -// int outStartRange; // output start year for range file -// int outStartOcc; // output start year for occupancy file - int outStartPop; // output start year for population file - int outStartInd; // output start year for individuals file - int outStartGenetic; // output start year for genetics file - int outStartTraitCell; // output start year for traits by cell file + int batchNum; // batch number + int simulation; // simulation no. + int reps; // no. of replicates + int years; // no. of years + int outStartPop; // output start year for population file + int outStartInd; // output start year for individuals file + int outStartTraitCell; // output start year for traits by cell file int outStartTraitRow; // output start year for traits by row file - int outStartConn; // output start year for connectivity matrix - int outIntRange; // output interval for range file - int outIntOcc; // output interval for occupancy file - int outIntPop; // output interval for population file - int outIntInd; // output interval for individuals file - int outIntGenetic; // output interval for genetics file + int outStartConn; // output start year for connectivity matrix + int outIntRange; // output interval for range file + int outIntOcc; // output interval for occupancy file + int outIntPop; // output interval for population file + int outIntInd; // output interval for individuals file int outIntTraitCell; // output interval for traits by cell file int outIntTraitRow; // output interval for traits by row file - int outIntConn; // output interval for connectivity matrix - int mapInt; // output interval for maps - int traitInt; // output interval for evolving traits maps - int slowFactor; // to reduce speed of movement paths on screen - bool batchMode; // - bool absorbing; // landscape boundary and no-data regions are - // absorbing boundaries - bool outRange; // produce output range file? - bool outOccup; // produce output occupancy file? - bool outPop; // produce output population file? - bool outInds; // produce output individuals file? - bool outGenetics; // produce output genetics file? - short outGenType; // produce output genetics for: 0 = juveniles only - // 1 = all individuals, 2 = adults (i.e. final stage) only - bool outGenXtab; // produce output genetics as a cross table? + int outIntConn; // output interval for connectivity matrix + int traitInt; // output interval for evolving traits maps + bool batchMode; + bool absorbing; // landscape boundary and no-data regions are absorbing boundaries + bool outRange; // produce output range file? + bool outOccup; // produce output occupancy file? + bool outPop; // produce output population file? + bool outInds; // produce output individuals file? bool outTraitsCells; // produce output summary traits by cell file? bool outTraitsRows; // produce output summary traits by row (y) file? - bool outConnect; // produce output connectivity file? - bool saveMaps; // save landscape/population maps? + bool outConnect; // produce output connectivity file? bool saveVisits; // save dispersal visits heat maps? #if RS_RCPP int outStartPaths; int outIntPaths; bool outPaths; - bool ReturnPopRaster; + bool ReturnPopMatrix; + bool ReturnPopDataFrame; bool CreatePopFile; + Rcpp::LogicalVector ReturnStages; #endif - bool drawLoaded; // draw initial distribution on landscape/population maps? - bool saveTraitMaps; // save summary traits maps? - bool viewLand; // view landscape map on screen? - bool viewPatch; // view map of landscape patches on screen? - bool viewGrad; // view gradient map on screen? - bool viewCosts; // view costs map on screen? - bool viewPop; // view population density on landscape map on screen? - bool viewTraits; // view summary traits map(s) on screen? - bool viewPaths; // view individual movement paths on screen? - bool viewGraph; // view population/occupancy graph on screen? - string dir; // full name of working directory - + string dir; // full name of working directory + bool fixReplicateSeed; + string patchSamplingOption; + bool outputGenes; + int outputGenesStart; + int outputGenesInterval; + bool outputGlobalFst; + bool outPairwiseFst; + bool outputPerLocusFst; + int outputPairwiseFstStart; + int outputGlobalFstStart; + int outputPairwiseFstInterval; + int outputGlobalFstInterval; }; -#if RSDEBUG -extern ofstream DEBUGLOG; -void DebugGUI(string); -#endif +extern RSrandom* pRandom; //--------------------------------------------------------------------------- #endif diff --git a/Patch.cpp b/Patch.cpp index 60421c4..f67d5ec 100644 --- a/Patch.cpp +++ b/Patch.cpp @@ -1,47 +1,49 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - -//--------------------------------------------------------------------------- + + + //--------------------------------------------------------------------------- #include "Patch.h" //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- -Patch::Patch(int seqnum,int num) +Patch::Patch(int seqnum, int num) { -patchSeqNum = seqnum; patchNum = num; nCells = 0; -xMin = yMin = 999999999; xMax = yMax = 0; x = y = 0; -subCommPtr = 0; -localK = 0.0; -for (int sex = 0; sex < NSEXES; sex++) { - nTemp[sex] = 0; -} -changed = false; + patchSeqNum = seqnum; patchNum = num; nCells = 0; + xMin = yMin = 999999999; xMax = yMax = 0; x = y = 0; +subCommPtr = nullptr; + localK = 0.0; + for (int sex = 0; sex < gMaxNbSexes; sex++) { + nTemp[sex] = 0; + } + localDemoScaling.assign(nDSlayer,1.0); + changed = false; } Patch::~Patch() { -cells.clear(); -popns.clear(); + cells.clear(); + popns.clear(); + localDemoScaling.clear(); } int Patch::getSeqNum(void) { return patchSeqNum; } @@ -51,68 +53,68 @@ int Patch::getPatchNum(void) { return patchNum; } int Patch::getNCells(void) { return nCells; } patchLimits Patch::getLimits(void) { -patchLimits p; -p.xMin = xMin; p.xMax = xMax; p.yMin = yMin; p.yMax = yMax; -return p; + patchLimits p; + p.xMin = xMin; p.xMax = xMax; p.yMin = yMin; p.yMax = yMax; + return p; } // Does the patch fall (partially) within a specified rectangle? -bool Patch::withinLimits(patchLimits rect){ -locn loc; -if (xMin <= rect.xMax && xMax >= rect.xMin && yMin <= rect.yMax && yMax >= rect.yMin) { - // patch is within the rectangle UNLESS it is irregular in shape and lies at a corner - // of the rectangle - if ((xMin >= rect.xMin && xMax <= rect.xMax) - || (yMin >= rect.yMin && yMax <= rect.yMax)) { - // patch lies within or along an edge of the initialistaion rectangle - return true; - } - else { - // check for any cell of the patch lying within the rectangle - int ncells = (int)cells.size(); - for (int i = 0; i < ncells; i++) { - loc = getCellLocn(i); - if (loc.x >= rect.xMin && loc.x <= rect.xMax - && loc.y >= rect.yMin && loc.y <= rect.yMax) { - // cell lies within the rectangle - return true; - } +bool Patch::withinLimits(patchLimits rect) { + locn loc; + if (xMin <= rect.xMax && xMax >= rect.xMin && yMin <= rect.yMax && yMax >= rect.yMin) { + // patch is within the rectangle UNLESS it is irregular in shape and lies at a corner + // of the rectangle + if ((xMin >= rect.xMin && xMax <= rect.xMax) + || (yMin >= rect.yMin && yMax <= rect.yMax)) { + // patch lies within or along an edge of the initialistaion rectangle + return true; + } + else { + // check for any cell of the patch lying within the rectangle + int ncells = (int)cells.size(); + for (int i = 0; i < ncells; i++) { + loc = getCellLocn(i); + if (loc.x >= rect.xMin && loc.x <= rect.xMax + && loc.y >= rect.yMin && loc.y <= rect.yMax) { + // cell lies within the rectangle + return true; } } } -return false; + } + return false; } // Reset minimum and maximum co-ordinates of the patch if it has been changed void Patch::resetLimits(void) { -if (changed) { - // remove any deleted cells - std::vector newcells; // for all retained and added cells - int ncells = (int)cells.size(); - for (int i = 0; i < ncells; i++) { - if (cells[i] != NULL) { - newcells.push_back(cells[i]); + if (changed) { + // remove any deleted cells + std::vector newcells; // for all retained and added cells + int ncells = (int)cells.size(); + for (int i = 0; i < ncells; i++) { + if (cells[i] != NULL) { + newcells.push_back(cells[i]); + } } + cells.clear(); + cells = newcells; + // reset patch limits + locn loc; + xMin = yMin = 999999999; xMax = yMax = 0; + ncells = (int)cells.size(); + for (int i = 0; i < ncells; i++) { + loc = getCellLocn(i); + if (loc.x < xMin) xMin = loc.x; + if (loc.x > xMax) xMax = loc.x; + if (loc.y < yMin) yMin = loc.y; + if (loc.y > yMax) yMax = loc.y; + } + changed = false; } - cells.clear(); - cells = newcells; - // reset patch limits - locn loc; - xMin = yMin = 999999999; xMax = yMax = 0; - ncells = (int)cells.size(); - for (int i = 0; i < ncells; i++) { - loc = getCellLocn(i); - if (loc.x < xMin) xMin = loc.x; - if (loc.x > xMax) xMax = loc.x; - if (loc.y < yMin) yMin = loc.y; - if (loc.y > yMax) yMax = loc.y; - } - changed = false; -} } // Add a cell to the patch -void Patch::addCell(Cell* pCell,int x,int y) { +void Patch::addCell(Cell* pCell, int x, int y) { cells.push_back(pCell); nCells++; if (x < xMin) xMin = x; @@ -123,231 +125,244 @@ void Patch::addCell(Cell* pCell,int x,int y) { // Calculate the total carrying capacity (no. of individuals) and // centroid co-ordinates of the patch -void Patch::setCarryingCapacity(Species *pSpecies,patchLimits landlimits, - float epsGlobal,short nHab,short rasterType,short landIx,bool gradK) { -envStochParams env = paramsStoch->getStoch(); -//Cell *pCell; -locn loc; -int xsum,ysum; -short hx; -float k,q,envval; - -localK = 0.0; // no. of suitable cells (unadjusted K > 0) in the patch -int nsuitable = 0; -double mean; - -#if RSDEBUG -//DEBUGLOG << "Patch::setCarryingCapacity(): patchNum=" << patchNum -// << " xMin=" << xMin << " yMin=" << yMin << " xMax=" << xMax << " yMax=" << yMax -// << endl; -#endif - -if (xMin > landlimits.xMax || xMax < landlimits.xMin -|| yMin > landlimits.yMax || yMax < landlimits.yMin) { - // patch lies wholely outwith current landscape limits - // NB the next statement is unnecessary, as localK has been set to zero above - // retained only for consistency in standard variant - localK = 0.0; -#if RSDEBUG -//DEBUGLOG << "Patch::setCarryingCapacity(): patchNum=" << patchNum -// << " localK=" << localK -// << endl; -#endif - return; -} - -int ncells = (int)cells.size(); -xsum = ysum = 0; -for (int i = 0; i < ncells; i++) { - if (gradK) // gradient in carrying capacity - envval = cells[i]->getEnvVal(); // environmental gradient value - else envval = 1.0; // no gradient effect - if (env.stoch && env.inK) { // environmental stochasticity in K - if (env.local) { -// pCell = getRandomCell(); -// if (pCell != 0) envval += pCell->getEps(); - envval += cells[i]->getEps(); - } - else { // global stochasticity - envval += epsGlobal; - } +void Patch::setCarryingCapacity(Species* pSpecies, patchLimits landlimits, + float epsGlobal, short nHab, short rasterType, short landIx, bool gradK) { + envStochParams env = paramsStoch->getStoch(); + locn loc; + int xsum, ysum; + short hx; + float k, q, envval; + + localK = 0.0; // no. of suitable cells (unadjusted K > 0) in the patch + int nsuitable = 0; + double mean; + + if (xMin > landlimits.xMax || xMax < landlimits.xMin + || yMin > landlimits.yMax || yMax < landlimits.yMin) { + // patch lies wholely outwith current landscape limits + // NB the next statement is unnecessary, as localK has been set to zero above + // retained only for consistency in standard variant + localK = 0.0; + return; } - switch (rasterType) { - case 0: // habitat codes - hx = cells[i]->getHabIndex(landIx); - k = pSpecies->getHabK(hx); - if (k > 0.0) { - nsuitable++; - localK += envval * k; - } - break; - case 1: // cover % - k = 0.0; - for (int j = 0; j < nHab; j++) { // loop through cover layers - q = cells[i]->getHabitat(j); - k += q * pSpecies->getHabK(j) / 100.0f; - } - if (k > 0.0) { - nsuitable++; - localK += envval * k; + + int ncells = (int)cells.size(); + xsum = ysum = 0; + for (int i = 0; i < ncells; i++) { + if (gradK) // gradient in carrying capacity + envval = cells[i]->getEnvVal(); // environmental gradient value + else envval = 1.0; // no gradient effect + if (env.stoch && env.inK) { // environmental stochasticity in K + if (env.local) { + envval += cells[i]->getEps(); + } + else { // global stochasticity + envval += epsGlobal; + } } - break; - case 2: // habitat quality - q = cells[i]->getHabitat(landIx); - if (q > 0.0) { - nsuitable++; - localK += envval * pSpecies->getHabK(0) * q / 100.0f; + switch (rasterType) { + case 0: // habitat codes + hx = cells[i]->getHabIndex(landIx); + k = pSpecies->getHabK(hx); + if (k > 0.0) { + nsuitable++; + localK += envval * k; + } + break; + case 1: // cover % + k = 0.0; + for (int j = 0; j < nHab; j++) { // loop through cover layers + q = cells[i]->getHabitat(j); + k += q * pSpecies->getHabK(j) / 100.0f; + } + if (k > 0.0) { + nsuitable++; + localK += envval * k; + } + break; + case 2: // habitat quality + q = cells[i]->getHabitat(landIx); + if (q > 0.0) { + nsuitable++; + localK += envval * pSpecies->getHabK(0) * q / 100.0f; + } + break; } - break; + loc = cells[i]->getLocn(); + xsum += loc.x; ysum += loc.y; } -#if RSDEBUG -//DEBUGLOG << "Patch::setCarryingCapacity(): patchNum=" << patchNum -// << " i=" << i << " hx=" << hx << " q=" << q << " k=" << k << " localK=" << localK -// << endl; -#endif - loc = cells[i]->getLocn(); - xsum += loc.x; ysum += loc.y; -} -#if RSDEBUG -//DEBUGLOG << "Patch::setCarryingCapacity(): patchNum=" << patchNum -// << " epsGlobal=" << epsGlobal << " localK=" << localK -// << endl; -#endif // calculate centroid co-ordinates -if (ncells > 0) { - mean = (double)xsum / (double)ncells; - x = (int)(mean + 0.5); - mean = (double)ysum / (double)ncells; - y = (int)(mean + 0.5); -} -if (env.stoch && env.inK) { // environmental stochasticity in K - // apply min and max limits to K over the whole patch - // NB limits have been stored as N/cell rather than N/ha - float limit; - limit = pSpecies->getMinMax(0) * (float)nsuitable; - if (localK < limit) localK = limit; -#if RSDEBUG -//DEBUGLOG << "Patch::setCarryingCapacity(): patchNum=" << patchNum -// << " limit=" << limit << " localK=" << localK -// << endl; -#endif - limit = pSpecies->getMinMax(1) * (float)nsuitable; - if (localK > limit) localK = limit; -#if RSDEBUG -//DEBUGLOG << "Patch::setCarryingCapacity(): patchNum=" << patchNum -// << " limit=" << limit << " localK=" << localK -// << endl; -#endif -} -#if RSDEBUG -//DEBUGLOG << "Patch::setCarryingCapacity(): patchNum=" << patchNum -// << " localK=" << localK -// << endl; -#endif + if (ncells > 0) { + mean = (double)xsum / (double)ncells; + x = (int)(mean + 0.5); + mean = (double)ysum / (double)ncells; + y = (int)(mean + 0.5); + } + if (env.stoch && env.inK) { // environmental stochasticity in K + // apply min and max limits to K over the whole patch + // NB limits have been stored as N/cell rather than N/ha + float limit; + limit = pSpecies->getMinMax(0) * (float)nsuitable; + if (localK < limit) localK = limit; + limit = pSpecies->getMinMax(1) * (float)nsuitable; + if (localK > limit) localK = limit; + } } float Patch::getK(void) { return localK; } +// SPATIALDEMOG +void Patch::setDemoScaling(std::vector ds) { + + std::for_each(ds.begin(), ds.end(), [](float& perc){ if(perc < 0.0 || perc > 1.0) perc=1; }); + + localDemoScaling.assign(ds.begin(), ds.end()); + + return; +} + +std::vector Patch::getDemoScaling(void) { return localDemoScaling; } + +void Patch::setPatchDemoScaling(short landIx, patchLimits landlimits) { + + // if patch wholly outside current landscape boundaries + if (xMin > landlimits.xMax || xMax < landlimits.xMin + || yMin > landlimits.yMax || yMax < landlimits.yMin) { + localDemoScaling.assign(nDSlayer,0.0); // set all local scales to zero + return; + } + + // loop through constituent cells of the patch + int ncells = (int)cells.size(); + std::vector patchDS(nDSlayer, 0.0); + std::vector cellDS(nDSlayer, 0.0); + + for (int i = 0; i < ncells; i++) { + cellDS = cells[i]->getDemoScaling(landIx); // is that ok? + + //add cell value to patch value + for (int ly = 0; ly < nDSlayer; ly++) { + patchDS[ly] += cellDS[ly]; + } + } + + // take mean over cells and divide by 100 to scale to range [0,1] + for (int ly = 0; ly < nDSlayer; ly++) { + patchDS[ly] = patchDS[ly] / ncells / 100.0f; + } + + // set values + setDemoScaling(patchDS); + + return; +} +//SPATIALDEMOG + // Return co-ordinates of a specified cell locn Patch::getCellLocn(int ix) { -locn loc; loc.x = -666; loc.y = -666; -int ncells = (int)cells.size(); -if (ix >= 0 && ix < ncells) { - loc = cells[ix]->getLocn(); -} -return loc; + locn loc; loc.x = -666; loc.y = -666; + int ncells = (int)cells.size(); + if (ix >= 0 && ix < ncells) { + loc = cells[ix]->getLocn(); + } + return loc; } // Return pointer to a specified cell -Cell* Patch::getCell(int ix) { -int ncells = (int)cells.size(); -if (ix >= 0 && ix < ncells) return cells[ix]; -else return 0; +Cell* Patch::getCell(int ix) { + int ncells = (int)cells.size(); + if (ix >= 0 && ix < ncells) return cells[ix]; + else return 0; } // Return co-ordinates of patch centroid locn Patch::getCentroid(void) { -locn loc; loc.x = x; loc.y = y; -return loc; + locn loc; loc.x = x; loc.y = y; + return loc; } // Select a Cell within the Patch at random, and return pointer to it // For a cell-based model, this will be the only Cell Cell* Patch::getRandomCell(void) { -Cell *pCell = 0; -int ix; -int ncells = (int)cells.size(); -if (ncells > 0) { - if (ncells == 1) ix = 0; - else ix = pRandom->IRandom(0,ncells-1); - pCell = cells[ix]; -} -return pCell; + Cell* pCell = 0; + int ix; + int ncells = (int)cells.size(); + if (ncells > 0) { + if (ncells == 1) ix = 0; + else ix = pRandom->IRandom(0, ncells - 1); + pCell = cells[ix]; + } + return pCell; } // Remove a cell from the patch void Patch::removeCell(Cell* pCell) { -int ncells = (int)cells.size(); -for (int i = 0; i < ncells; i++) { - if (pCell == cells[i]) { - cells[i] = NULL; i = ncells; - nCells--; - changed = true; + int ncells = (int)cells.size(); + for (int i = 0; i < ncells; i++) { + if (pCell == cells[i]) { + cells[i] = NULL; i = ncells; + nCells--; + changed = true; + } } } -} -void Patch::setSubComm(intptr sc) -{ subCommPtr = sc; } +void Patch::setSubComm(SubCommunity* sc) +{ + subCommPtr = sc; +} -// Get pointer to corresponding Sub-community (cast as an integer) -intptr Patch::getSubComm(void) +// Get pointer to corresponding Sub-community +SubCommunity* Patch::getSubComm(void) { return subCommPtr; } +#ifdef _OPENMP +std::unique_lock Patch::lockPopns() { + return std::unique_lock(popns_mutex); +} +#endif + void Patch::addPopn(patchPopn pop) { -popns.push_back(pop); + popns.push_back(pop); } -// Return pointer (cast as integer) to the Population of the specified Species -intptr Patch::getPopn(intptr sp) +// Return pointer to the Population of the specified Species +Population* Patch::getPopn(Species *sp) { -int npops = (int)popns.size(); -for (int i = 0; i < npops; i++) { - if (popns[i].pSp == sp) return popns[i].pPop; -} -return 0; + int npops = (int)popns.size(); + for (int i = 0; i < npops; i++) { + if (popns[i].pSp == sp) return popns[i].pPop; + } + return 0; } void Patch::resetPopn(void) { -popns.clear(); + popns.clear(); } void Patch::resetPossSettlers(void) { -for (int sex = 0; sex < NSEXES; sex++) { - nTemp[sex] = 0; -} + for (int sex = 0; sex < gMaxNbSexes; sex++) { + nTemp[sex] = 0; + } } // Record the presence of a potential settler within the Patch -void Patch::incrPossSettler(Species *pSpecies,int sex) { -#if RSDEBUG -//DEBUGLOG << "Patch::incrPossSettler(): 5555: patchNum = " << patchNum -// << " sex = " << sex << endl; -#endif +void Patch::incrPossSettler(Species* pSpecies, int sex) { // NOTE: THE FOLLOWING OPERATION WILL NEED TO BE MADE SPECIES-SPECIFIC... -if (sex >= 0 && sex < NSEXES) { - nTemp[sex]++; -} + if (sex >= 0 && sex < gMaxNbSexes) { + nTemp[sex]++; + } } // Get number of a potential settlers within the Patch -int Patch::getPossSettlers(Species *pSpecies,int sex) { -#if RSDEBUG -//DEBUGLOG << "Patch::getPossSettlers(): 5555: patchNum = " << patchNum -// << " sex = " << sex << endl; -#endif +int Patch::getPossSettlers(Species* pSpecies, int sex) { // NOTE: THE FOLLOWING OPERATION WILL NEED TO BE MADE SPECIES-SPECIFIC... -if (sex >= 0 && sex < NSEXES) return nTemp[sex]; -else return 0; + if (sex >= 0 && sex < gMaxNbSexes) return nTemp[sex]; + else return 0; +} + +bool Patch::speciesIsPresent(Species* pSpecies) { + const auto pPop = this->getPopn(pSpecies); + return pPop != 0; } //--------------------------------------------------------------------------- diff --git a/Patch.h b/Patch.h index 4095908..a26dd28 100644 --- a/Patch.h +++ b/Patch.h @@ -1,66 +1,66 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Tho Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - -/*------------------------------------------------------------------------------ -RangeShifter v2.0 Patch -Implements the class: Patch + /*------------------------------------------------------------------------------ -A patch is a collection of one or more Cells in the the gridded Landscape, -which together provide the area in which a single demographic unit of a Species, -i.e. a Population, can reproduce. One or more Populations (of different Species) -form a Sub-community associated with the Patch. + RangeShifter v2.0 Patch -There is no requirement that all the Cells be adjacent, although in practice -that would usually be the case. + Implements the class: Patch -Each Patch must have a unique positive integer id number supplied by the user, -and the matrix, i.e. any part of the landscape which is not a breeding patch, -is represented by Patch 0. However, as patch numbers need not be sequential, -an internal sequential number is also applied. + A patch is a collection of one or more Cells in the the gridded Landscape, + which together provide the area in which a single demographic unit of a Species, + i.e. a Population, can reproduce. One or more Populations (of different Species) + form a Sub-community associated with the Patch. -For a 'cell-based model', the user supplies no patch numbers, and a separate -Patch is generated internally for each Cell, i.e. the 'cell-based model' is a -special case of the 'patch-based model' in which each Patch has a single Cell. -Moreover, there is also the 'matrix' Patch 0, which has no cells, but which -holds the disperser population whilst its Individuals are in transit. + There is no requirement that all the Cells be adjacent, although in practice + that would usually be the case. -In a true patch-based model, each Patch hold a list of its constituent Cells, -EXCEPT for the matrix Patch 0. This is because that list would be extremely -long for a very large landscape in which suitable patches are small and/or rare, -and removing Cells from it if the landscape is dynamic would be inefficient. + Each Patch must have a unique positive integer id number supplied by the user, + and the matrix, i.e. any part of the landscape which is not a breeding patch, + is represented by Patch 0. However, as patch numbers need not be sequential, + an internal sequential number is also applied. -For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. -and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. -Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 + For a 'cell-based model', the user supplies no patch numbers, and a separate + Patch is generated internally for each Cell, i.e. the 'cell-based model' is a + special case of the 'patch-based model' in which each Patch has a single Cell. + Moreover, there is also the 'matrix' Patch 0, which has no cells, but which + holds the disperser population whilst its Individuals are in transit. -Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + In a true patch-based model, each Patch hold a list of its constituent Cells, + EXCEPT for the matrix Patch 0. This is because that list would be extremely + long for a very large landscape in which suitable patches are small and/or rare, + and removing Cells from it if the landscape is dynamic would be inefficient. -Last updated: 25 June 2021 by Steve Palmer + For full details of RangeShifter, please see: + Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. + and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial + eco-evolutionary dynamics and species responses to environmental changes. + Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 -------------------------------------------------------------------------------*/ + Authors: Greta Bocedi & Steve Palmer, University of Aberdeen + + Last updated: 25 June 2021 by Steve Palmer + + ------------------------------------------------------------------------------*/ #ifndef PatchH #define PatchH @@ -72,16 +72,25 @@ using namespace std; #include "Cell.h" #include "Species.h" -//--------------------------------------------------------------------------- +#ifdef _OPENMP +#include +#include +#endif + + //--------------------------------------------------------------------------- + +class Population; +class SubCommunity; struct patchLimits { - int xMin,xMax,yMin,yMax; + int xMin, xMax, yMin, yMax; }; struct patchPopn { - intptr pSp,pPop; // pointers to Species and Population cast as integers + Species *pSp; // pointers to Species + Population *pPop; // pointers to Population }; -class Patch{ +class Patch { public: Patch( int, // internal sequential number @@ -98,7 +107,7 @@ class Patch{ void resetLimits(void); // Reset minimum and maximum co-ordinates of the patch void addCell( Cell*, // pointer to the Cell to be added to the Patch - int,int // x (column) and y (row) co-ordinates of the Cell + int, int // x (column) and y (row) co-ordinates of the Cell ); locn getCellLocn( // Return co-ordinates of a specified cell int // index no. of the Cell within the vector cells @@ -112,14 +121,17 @@ class Patch{ ); Cell* getRandomCell(void); void setSubComm( - intptr // pointer to the Sub-community cast as an integer + SubCommunity * // pointer to the Sub-community ); - intptr getSubComm(void); + SubCommunity *getSubComm(void); +#ifdef _OPENMP + std::unique_lock lockPopns(); +#endif void addPopn( - patchPopn // structure holding pointers to Species and Population cast as integers + patchPopn // structure holding pointers to Species and Population ); - intptr getPopn( // return pointer (cast as integer) to the Population of the Species - intptr // pointer to Species cast as integer + Population *getPopn( // return pointer to the Population of the Species + Species * // pointer to Species ); void resetPopn(void); void resetPossSettlers(void); @@ -141,34 +153,42 @@ class Patch{ bool // TRUE if there is a gradient in carrying capacity across the Landscape ); float getK(void); - // dummy function for batch version - void drawCells(float,int,rgb); + bool speciesIsPresent(Species* pSpecies); + void setDemoScaling(std::vector ); + std::vector getDemoScaling(void); + void setPatchDemoScaling(short, patchLimits); // calculate demog. scalings of patch from its cells //TODO arguments - private: +private: int patchSeqNum;// sequential patch number - patch 0 is reserved for the inter-patch matrix int patchNum; // patch number as supplied by the user (not forced to be sequential) int nCells; // no. of cells in the patch - int xMin,xMax,yMin,yMax; // min and max cell co-ordinates - int x,y; // centroid co-ordinates (approx.) - intptr subCommPtr; // pointer (cast as integer) to sub-community associated with the patch + int xMin, xMax, yMin, yMax; // min and max cell co-ordinates + int x, y; // centroid co-ordinates (approx.) + SubCommunity *subCommPtr; // pointer to sub-community associated with the patch // NOTE: FOR MULTI-SPECIES MODEL, PATCH WILL NEED TO STORE K FOR EACH SPECIES float localK; // patch carrying capacity (individuals) + std::vector localDemoScaling; bool changed; -// NOTE: THE FOLLOWING ARRAY WILL NEED TO BE MADE SPECIES-SPECIFIC... - short nTemp[NSEXES]; // no. of potential settlers in each sex + // NOTE: THE FOLLOWING ARRAY WILL NEED TO BE MADE SPECIES-SPECIFIC... +#ifdef _OPENMP + std::atomic nTemp[gMaxNbSexes]; // no. of potential settlers in each sex +#else + short nTemp[gMaxNbSexes]; // no. of potential settlers in each sex +#endif std::vector cells; std::vector popns; +#ifdef _OPENMP + std::mutex popns_mutex; +#endif }; //--------------------------------------------------------------------------- -extern paramStoch *paramsStoch; -extern RSrandom *pRandom; +extern paramStoch* paramsStoch; +extern RSrandom* pRandom; -#if RSDEBUG -extern ofstream DEBUGLOG; -#endif +extern short nDSlayer; #endif diff --git a/Population.cpp b/Population.cpp index 3dbb6a2..0b42b6e 100644 --- a/Population.cpp +++ b/Population.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -23,6 +23,8 @@ //--------------------------------------------------------------------------- #include "Population.h" + +#include //--------------------------------------------------------------------------- ofstream outPop; @@ -57,15 +59,14 @@ Population::Population(Species* pSp, Patch* pPch, int ninds, int resol) pPatch = pPch; // record the new population in the patch patchPopn pp; - pp.pSp = (intptr)pSpecies; pp.pPop = (intptr)this; + pp.pSp = pSpecies; pp.pPop = this; pPatch->addPopn(pp); - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); - genomeData gen = pSpecies->getGenomeData(); initParams init = paramsInit->getInit(); // determine no. of stages and sexes of species to initialise @@ -78,14 +79,14 @@ Population::Population(Species* pSp, Patch* pPch, int ninds, int resol) else { nSexes = 2; probmale = dem.propMales; } // set up population sub-totals - for (int stg = 0; stg < NSTAGES; stg++) { - for (int sex = 0; sex < NSEXES; sex++) { + for (int stg = 0; stg < gMaxNbStages; stg++) { + for (int sex = 0; sex < gMaxNbSexes; sex++) { nInds[stg][sex] = 0; } } // set up local copy of minimum age table - short minAge[NSTAGES][NSEXES]; + short minAge[gMaxNbStages][gMaxNbSexes]; for (int stg = 0; stg < nStages; stg++) { for (int sex = 0; sex < nSexes; sex++) { if (dem.stageStruct) { @@ -174,21 +175,15 @@ Population::Population(Species* pSp, Patch* pPch, int ninds, int resol) } } else age = stg; -#if RSDEBUG - // NOTE: CURRENTLY SETTING ALL INDIVIDUALS TO RECORD NO. OF STEPS ... - inds.push_back(new Individual(pCell, pPatch, stg, age, sstruct.repInterval, - probmale, true, trfr.moveType)); -#else - inds.push_back(new Individual(pCell, pPatch, stg, age, sstruct.repInterval, - probmale, trfr.moveModel, trfr.moveType)); -#endif - sex = inds[nindivs + i]->getSex(); - if (emig.indVar || trfr.indVar || sett.indVar || gen.neutralMarkers) - { - // individual variation - set up genetics - inds[nindivs + i]->setGenes(pSpecies, resol); + + Individual* newInd = new Individual(pSpecies, pCell, pPatch, stg, age, sstruct.repInterval, + probmale, trfr.usesMovtProc, trfr.moveType); + + if (pSpecies->getNTraits() > 0) { + newInd->setUpGenes(pSpecies, resol); } - nInds[stg][sex]++; + inds.push_back(newInd); + nInds[stg][newInd->getSex()]++; } } } @@ -204,78 +199,252 @@ Population::~Population(void) { if (juvs[i] != NULL) delete juvs[i]; } juvs.clear(); + int nsampledInds = (int)sampledInds.size(); + for (int i = 0; i < nsampledInds; i++) { + if (sampledInds[i] != NULL) sampledInds[i]=NULL; } + sampledInds.clear(); +} + +traitsums Population::getIndTraitsSums(Species* pSpecies) { + + traitsums ts = traitsums(); + for (int sex = 0; sex < gMaxNbSexes; sex++) { + ts.ninds[sex] = 0; + ts.sumD0[sex] = ts.ssqD0[sex] = 0.0; + ts.sumAlpha[sex] = ts.ssqAlpha[sex] = 0.0; + ts.sumBeta[sex] = ts.ssqBeta[sex] = 0.0; + ts.sumDist1[sex] = ts.ssqDist1[sex] = 0.0; + ts.sumDist2[sex] = ts.ssqDist2[sex] = 0.0; + ts.sumProp1[sex] = ts.ssqProp1[sex] = 0.0; + ts.sumDP[sex] = ts.ssqDP[sex] = 0.0; + ts.sumGB[sex] = ts.ssqGB[sex] = 0.0; + ts.sumAlphaDB[sex] = ts.ssqAlphaDB[sex] = 0.0; + ts.sumBetaDB[sex] = ts.ssqBetaDB[sex] = 0.0; + ts.sumStepL[sex] = ts.ssqStepL[sex] = 0.0; + ts.sumRho[sex] = ts.ssqRho[sex] = 0.0; + ts.sumS0[sex] = ts.ssqS0[sex] = 0.0; + ts.sumAlphaS[sex] = ts.ssqAlphaS[sex] = 0.0; + ts.sumBetaS[sex] = ts.ssqBetaS[sex] = 0.0; + ts.sumGeneticFitness[sex] = ts.ssqGeneticFitness[sex] = 0.0; + } -traitsums Population::getTraits(Species* pSpecies) { - int g; - traitsums ts; - for (int i = 0; i < NSEXES; i++) { - ts.ninds[i] = 0; - ts.sumD0[i] = ts.ssqD0[i] = 0.0; - ts.sumAlpha[i] = ts.ssqAlpha[i] = 0.0; ts.sumBeta[i] = ts.ssqBeta[i] = 0.0; - ts.sumDist1[i] = ts.ssqDist1[i] = 0.0; ts.sumDist2[i] = ts.ssqDist2[i] = 0.0; - ts.sumProp1[i] = ts.ssqProp1[i] = 0.0; - ts.sumDP[i] = ts.ssqDP[i] = 0.0; - ts.sumGB[i] = ts.ssqGB[i] = 0.0; - ts.sumAlphaDB[i] = ts.ssqAlphaDB[i] = 0.0; - ts.sumBetaDB[i] = ts.ssqBetaDB[i] = 0.0; - ts.sumStepL[i] = ts.ssqStepL[i] = 0.0; ts.sumRho[i] = ts.ssqRho[i] = 0.0; - ts.sumS0[i] = ts.ssqS0[i] = 0.0; - ts.sumAlphaS[i] = ts.ssqAlphaS[i] = 0.0; ts.sumBetaS[i] = ts.ssqBetaS[i] = 0.0; - } - - demogrParams dem = pSpecies->getDemogr(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); - int ninds = (int)inds.size(); - for (int i = 0; i < ninds; i++) { - int sex = inds[i]->getSex(); - if (emig.sexDep || trfr.sexDep || sett.sexDep) g = sex; else g = 0; - ts.ninds[g] += 1; + for (auto& ind : inds) { + + int sex = ind->getSex(); + ts.ninds[sex] += 1; + // emigration traits - emigTraits e = inds[i]->getEmigTraits(); - if (emig.sexDep) g = sex; else g = 0; - ts.sumD0[g] += e.d0; ts.ssqD0[g] += e.d0 * e.d0; - ts.sumAlpha[g] += e.alpha; ts.ssqAlpha[g] += e.alpha * e.alpha; - ts.sumBeta[g] += e.beta; ts.ssqBeta[g] += e.beta * e.beta; + emigTraits e = ind->getIndEmigTraits(); + ts.sumD0[sex] += e.d0; + ts.ssqD0[sex] += e.d0 * e.d0; + ts.sumAlpha[sex] += e.alpha; + ts.ssqAlpha[sex] += e.alpha * e.alpha; + ts.sumBeta[sex] += e.beta; + ts.ssqBeta[sex] += e.beta * e.beta; + // transfer traits - trfrKernTraits k = inds[i]->getKernTraits(); - if (trfr.sexDep) g = sex; else g = 0; - ts.sumDist1[g] += k.meanDist1; ts.ssqDist1[g] += k.meanDist1 * k.meanDist1; - ts.sumDist2[g] += k.meanDist2; ts.ssqDist2[g] += k.meanDist2 * k.meanDist2; - ts.sumProp1[g] += k.probKern1; ts.ssqProp1[g] += k.probKern1 * k.probKern1; - trfrSMSTraits sms = inds[i]->getSMSTraits(); - g = 0; // CURRENTLY INDIVIDUAL VARIATION CANNOT BE SEX-DEPENDENT - ts.sumDP[g] += sms.dp; ts.ssqDP[g] += sms.dp * sms.dp; - ts.sumGB[g] += sms.gb; ts.ssqGB[g] += sms.gb * sms.gb; - ts.sumAlphaDB[g] += sms.alphaDB; ts.ssqAlphaDB[g] += sms.alphaDB * sms.alphaDB; - ts.sumBetaDB[g] += sms.betaDB; ts.ssqBetaDB[g] += sms.betaDB * sms.betaDB; - trfrCRWTraits c = inds[i]->getCRWTraits(); - g = 0; // CURRENTLY INDIVIDUAL VARIATION CANNOT BE SEX-DEPENDENT - ts.sumStepL[g] += c.stepLength; ts.ssqStepL[g] += c.stepLength * c.stepLength; - ts.sumRho[g] += c.rho; ts.ssqRho[g] += c.rho * c.rho; + if (trfr.usesMovtProc) { + + switch (trfr.moveType) { + + case 1: { // SMS + trfrSMSTraits sms = ind->getIndSMSTraits(); + ts.sumDP[sex] += sms.dp; + ts.ssqDP[sex] += sms.dp * sms.dp; + ts.sumGB[sex] += sms.gb; + ts.ssqGB[sex] += sms.gb * sms.gb; + ts.sumAlphaDB[sex] += sms.alphaDB; + ts.ssqAlphaDB[sex] += sms.alphaDB * sms.alphaDB; + ts.sumBetaDB[sex] += sms.betaDB; + ts.ssqBetaDB[sex] += sms.betaDB * sms.betaDB; + break; + } + case 2: { + trfrCRWTraits c = ind->getIndCRWTraits(); + ts.sumStepL[sex] += c.stepLength; + ts.ssqStepL[sex] += c.stepLength * c.stepLength; + ts.sumRho[sex] += c.rho; + ts.ssqRho[sex] += c.rho * c.rho; + break; + } + default: + throw runtime_error("usesMoveProcess is ON but moveType is neither 1 (SMS) or 2 (CRW)."); + break; + } + } + else { + trfrKernelParams k = ind->getIndKernTraits(); + ts.sumDist1[sex] += k.meanDist1; + ts.ssqDist1[sex] += k.meanDist1 * k.meanDist1; + ts.sumDist2[sex] += k.meanDist2; + ts.ssqDist2[sex] += k.meanDist2 * k.meanDist2; + ts.sumProp1[sex] += k.probKern1; + ts.ssqProp1[sex] += k.probKern1 * k.probKern1; + } // settlement traits - settleTraits s = inds[i]->getSettTraits(); - if (sett.sexDep) g = sex; else g = 0; - ts.sumS0[g] += s.s0; ts.ssqS0[g] += s.s0 * s.s0; - ts.sumAlphaS[g] += s.alpha; ts.ssqAlphaS[g] += s.alpha * s.alpha; - ts.sumBetaS[g] += s.beta; ts.ssqBetaS[g] += s.beta * s.beta; + settleTraits s = ind->getIndSettTraits(); + ts.sumS0[sex] += s.s0; + ts.ssqS0[sex] += s.s0 * s.s0; + ts.sumAlphaS[sex] += s.alpha; + ts.ssqAlphaS[sex] += s.alpha * s.alpha; + ts.sumBetaS[sex] += s.beta; + ts.ssqBetaS[sex] += s.beta * s.beta; + + double fitness = ind->getGeneticFitness(); + ts.sumGeneticFitness[sex] += fitness; + ts.ssqGeneticFitness[sex] += fitness * fitness; } - return ts; } +//int Population::getNInds() { return static_cast(inds.size()); } + +// ---------------------------------------------------------------------------------------- +// reset allele table +// ---------------------------------------------------------------------------------------- +void Population::resetPopNeutralTables() { + for (auto& entry : popNeutralCountTables) { + entry.reset(); + } +} + +// ---------------------------------------------------------------------------------------- +// Populate population-level NEUTRAL count tables +// Update allele occurrence and heterozygosity counts, and allele frequencies +// ---------------------------------------------------------------------------------------- +void Population::updatePopNeutralTables() { + + const int nLoci = pSpecies->getNPositionsForTrait(NEUTRAL); + const int nAlleles = pSpecies->getSpTrait(NEUTRAL)->getNbNeutralAlleles(); + const auto& positions = pSpecies->getSpTrait(NEUTRAL)->getGenePositions(); + const int ploidy = pSpecies->isDiploid() ? 2 : 1; + + // Create /reset empty tables + if (popNeutralCountTables.size() != 0) + resetPopNeutralTables(); + else { + popNeutralCountTables.reserve(nLoci); + + for (int l = 0; l < nLoci; l++) { + popNeutralCountTables.push_back(NeutralCountsTable(nAlleles)); + } + } -int Population::getNInds(void) { return (int)inds.size(); } + // Fill tallies for each locus + for (Individual* individual : sampledInds) { -popStats Population::getStats(void) + const auto trait = individual->getTrait(NEUTRAL); + int whichLocus = 0; + for (auto position : positions) { + + int alleleOnChromA = (int)trait->getAlleleValueAtLocus(0, position); + popNeutralCountTables[whichLocus].incrementTally(alleleOnChromA); + + if (ploidy == 2) { // second allele and heterozygosity + int alleleOnChromB = (int)trait->getAlleleValueAtLocus(1, position); + popNeutralCountTables[whichLocus].incrementTally(alleleOnChromB); + + bool isHetero = alleleOnChromA != alleleOnChromB; + if (isHetero) { + popNeutralCountTables[whichLocus].incrementHeteroTally(alleleOnChromA); + popNeutralCountTables[whichLocus].incrementHeteroTally(alleleOnChromB); + } + } + whichLocus++; + } + } + + // Fill frequencies + if (sampledInds.size() > 0) { + std::for_each( + popNeutralCountTables.begin(), + popNeutralCountTables.end(), + [&](NeutralCountsTable& thisLocus) -> void { + thisLocus.setFrequencies(static_cast(sampledInds.size()) * ploidy); + }); + } +} + +double Population::getAlleleFrequency(int thisLocus, int whichAllele) { + return popNeutralCountTables[thisLocus].getFrequency(whichAllele); +} + +int Population::getAlleleTally(int thisLocus, int whichAllele) { + return popNeutralCountTables[thisLocus].getTally(whichAllele); +} + +int Population::getHeteroTally(int thisLocus, int whichAllele) { + return popNeutralCountTables[thisLocus].getHeteroTally(whichAllele); +} + +// ---------------------------------------------------------------------------------------- +// Count number of heterozygotes loci in sampled individuals +// ---------------------------------------------------------------------------------------- +int Population::countHeterozygoteLoci() { + int nbHetero = 0; + if (pSpecies->isDiploid()) { + for (Individual* ind : sampledInds) { + const NeutralTrait* trait = (NeutralTrait*)(ind->getTrait(NEUTRAL)); + nbHetero += trait->countHeterozygoteLoci(); + } + } + return nbHetero; +} + +// ---------------------------------------------------------------------------------------- +// Count number of heterozygotes among sampled individuals for each locus +// ---------------------------------------------------------------------------------------- +vector Population::countNbHeterozygotesEachLocus() { + const auto& positions = pSpecies->getSpTrait(NEUTRAL)->getGenePositions(); + vector hetero(positions.size(), 0); + + if (pSpecies->isDiploid()) { + for (Individual* ind : sampledInds) { + const NeutralTrait* trait = (NeutralTrait*)ind->getTrait(NEUTRAL); + int counter = 0; + for (auto position : positions) { + hetero[counter] += trait->isHeterozygoteAtLocus(position); + counter++; + } + } + } + return hetero; +} + +// ---------------------------------------------------------------------------------------- +// Compute the expected heterozygosity for population +// ---------------------------------------------------------------------------------------- +double Population::computeHs() { + int nLoci = pSpecies->getNPositionsForTrait(NEUTRAL); + int nAlleles = pSpecies->getSpTrait(NEUTRAL)->getNbNeutralAlleles(); + double hs = 0; + double freq; + vector locihet(nLoci, 1); + + if (sampledInds.size() > 0) { + for (int thisLocus = 0; thisLocus < nLoci; ++thisLocus) { + for (int allele = 0; allele < nAlleles; ++allele) { + freq = getAlleleFrequency(thisLocus, allele); + freq *= freq; //squared frequencies (expected _homozygosity) + locihet[thisLocus] -= freq; // 1 - sum of p2 = expected heterozygosity + } + hs += locihet[thisLocus]; + } + } + return hs; +} + +popStats Population::getStats(std::vector localDemoScaling) { - popStats p; + popStats p = popStats(); int ninds; float fec; - bool breeders[2]; breeders[0] = breeders[1] = false; - demogrParams dem = pSpecies->getDemogr(); + bool breeders[2] = { false, false }; + demogrParams dem = pSpecies->getDemogrParams(); p.pSpecies = pSpecies; p.pPatch = pPatch; p.spNum = pSpecies->getSpNum(); @@ -286,11 +455,20 @@ popStats Population::getStats(void) for (int sex = 0; sex < nSexes; sex++) { ninds = nInds[stg][sex]; p.nNonJuvs += ninds; - if (ninds > 0) { if (pSpecies->stageStructured()) { - if (dem.repType == 2) fec = pSpecies->getFec(stg, sex); + if (dem.repType == 2) { + if (pSpecies->getFecSpatial() && pSpecies->getFecLayer(stg,sex)>=0){ + fec = pSpecies->getFec(stg,sex)*localDemoScaling[pSpecies->getFecLayer(stg,sex)]; + } + else fec = pSpecies->getFec(stg,sex); + } + else { + if (pSpecies->getFecSpatial() && pSpecies->getFecLayer(stg,0)>=0){ + fec = pSpecies->getFec(stg,0)*localDemoScaling[pSpecies->getFecLayer(stg,0)]; + } else fec = pSpecies->getFec(stg, 0); + } if (fec > 0.0) { breeders[sex] = true; p.nAdults += ninds; } } else breeders[sex] = true; @@ -309,25 +487,24 @@ popStats Population::getStats(void) Species* Population::getSpecies(void) { return pSpecies; } -int Population::totalPop(void) { - int t = 0; - for (int stg = 0; stg < nStages; stg++) { - for (int sex = 0; sex < nSexes; sex++) { - t += nInds[stg][sex]; - } - } - return t; +int Population::getNbInds() const { + return inds.size(); } -int Population::stagePop(int stg) { +int Population::getNbInds(int stg) const { int t = 0; - if (stg < 0 || stg >= nStages) return t; + if (stg < 0 || stg >= nStages) throw runtime_error("Attempt to get nb individuals for stage " + to_string(stg) + ", no such stage."); for (int sex = 0; sex < nSexes; sex++) { t += nInds[stg][sex]; } return t; } +int Population::getNbInds(int stg, int sex) const { + if (stg < 0 || stg >= nStages) throw runtime_error("Attempt to get nb individuals for stage " + to_string(stg) + ", no such stage."); + return nInds[stg][sex]; +} + //--------------------------------------------------------------------------- // Remove all Individuals void Population::extirpate(void) { @@ -350,7 +527,7 @@ void Population::extirpate(void) { //--------------------------------------------------------------------------- // Produce juveniles and hold them in the juvs vector -void Population::reproduction(const float localK, const float envval, const int resol) +void Population::reproduction(const float localK, const float envval, const int resol, std::vector localDemoScaling) { // get population size at start of reproduction @@ -364,27 +541,36 @@ void Population::reproduction(const float localK, const float envval, const int bool skipbreeding; envStochParams env = paramsStoch->getStoch(); - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); - genomeData gen = pSpecies->getGenomeData(); - simView v = paramsSim->getViews(); - if (dem.repType == 0) nsexes = 1; else nsexes = 2; + if (dem.repType == 0) + nsexes = 1; + else nsexes = 2; + // set up local copy of species fecundity table - float fec[NSTAGES][NSEXES]; + float fec[gMaxNbStages][gMaxNbSexes]; for (int stg = 0; stg < sstruct.nStages; stg++) { for (int sex = 0; sex < nsexes; sex++) { if (dem.stageStruct) { if (dem.repType == 1) { // simple sexual model // both sexes use fecundity recorded for females - fec[stg][sex] = pSpecies->getFec(stg, 0); + if (pSpecies->getFecSpatial() && pSpecies->getFecLayer(stg,0)>=0){ + fec[stg][sex] = pSpecies->getFec(stg,0)*localDemoScaling[pSpecies->getFecLayer(stg,0)]; + } + else fec[stg][sex] = pSpecies->getFec(stg,0); } + else { + if (pSpecies->getFecSpatial() && pSpecies->getFecLayer(stg,sex)>=0){ + fec[stg][sex] = pSpecies->getFec(stg,sex)*localDemoScaling[pSpecies->getFecLayer(stg,sex)]; + } else fec[stg][sex] = pSpecies->getFec(stg, sex); } + } else { // non-structured population if (stg == 1) fec[stg][sex] = dem.lambda; // adults else fec[stg][sex] = 0.0; // juveniles @@ -428,7 +614,7 @@ void Population::reproduction(const float localK, const float envval, const int } } else // not stage-specific - effect = (float)totalPop(); + effect = (float)getNbInds(); if (localK > 0.0) fec[stg][0] *= exp(-effect / localK); } } @@ -457,7 +643,7 @@ void Population::reproduction(const float localK, const float envval, const int } double propBreed; - Individual* father; + Individual* father = nullptr; std::vector fathers; switch (dem.repType) { @@ -487,17 +673,20 @@ void Population::reproduction(const float localK, const float envval, const int nj = (int)juvs.size(); pCell = pPatch->getRandomCell(); for (int j = 0; j < njuvs; j++) { -#if RSDEBUG - // NOTE: CURRENTLY SETTING ALL INDIVIDUALS TO RECORD NO. OF STEPS ... - juvs.push_back(new Individual(pCell, pPatch, 0, 0, 0, 0.0, true, trfr.moveType)); -#else - juvs.push_back(new Individual(pCell, pPatch, 0, 0, 0, 0.0, trfr.moveModel, trfr.moveType)); -#endif + + Individual* newJuv; + newJuv = new Individual(pSpecies, pCell, pPatch, 0, 0, 0, dem.propMales, trfr.usesMovtProc, trfr.moveType); + + if (pSpecies->getNTraits() > 0) { + newJuv->inheritTraits(pSpecies, inds[i], resol); + } + + if (!newJuv->isViable()) { + delete newJuv; + } + else { + juvs.push_back(newJuv); nInds[0][0]++; - if (emig.indVar || trfr.indVar || sett.indVar || gen.neutralMarkers) - { - // juv inherits genome from parent (mother) - juvs[nj + j]->setGenes(pSpecies, inds[i], 0, resol); } } } @@ -509,7 +698,6 @@ void Population::reproduction(const float localK, const float envval, const int case 2: // complex sexual model // count breeding females and males // add breeding males to list of potential fathers - nfemales = nmales = 0; for (int i = 0; i < ninds; i++) { ind = inds[i]->getStats(); @@ -553,6 +741,7 @@ void Population::reproduction(const float localK, const float envval, const int else expected = 0.0; // fails to breed if (expected <= 0.0) njuvs = 0; else njuvs = pRandom->Poisson(expected); + if (njuvs > 0) { nj = (int)juvs.size(); @@ -562,18 +751,21 @@ void Population::reproduction(const float localK, const float envval, const int father = fathers[rrr]; pCell = pPatch->getRandomCell(); for (int j = 0; j < njuvs; j++) { -#if RSDEBUG - // NOTE: CURRENTLY SETTING ALL INDIVIDUALS TO RECORD NO. OF STEPS ... - juvs.push_back(new Individual(pCell, pPatch, 0, 0, 0, dem.propMales, true, trfr.moveType)); -#else - juvs.push_back(new Individual(pCell, pPatch, 0, 0, 0, dem.propMales, trfr.moveModel, trfr.moveType)); -#endif - sex = juvs[nj + j]->getSex(); + Individual* newJuv; + + newJuv = new Individual(pSpecies, pCell, pPatch, 0, 0, 0, dem.propMales, trfr.usesMovtProc, trfr.moveType); + + if (pSpecies->getNTraits() > 0) { + newJuv->inheritTraits(pSpecies, inds[i], father, resol); + } + + if (!newJuv->isViable()) { + delete newJuv; + } + else { + juvs.push_back(newJuv); + sex = newJuv->getSex(); nInds[0][sex]++; - if (emig.indVar || trfr.indVar || sett.indVar || gen.neutralMarkers) - { - // juv inherits genome from parents - juvs[nj + j]->setGenes(pSpecies, inds[i], father, resol); } } } @@ -593,7 +785,7 @@ void Population::reproduction(const float localK, const float envval, const int // Following reproduction of ALL species, add juveniles to the population prior to dispersal void Population::fledge(void) { - demogrParams dem = pSpecies->getDemogr(); + demogrParams dem = pSpecies->getDemogrParams(); if (dem.stageStruct) { // juveniles are added to the individuals vector inds.insert(inds.end(), juvs.begin(), juvs.end()); @@ -607,75 +799,124 @@ void Population::fledge(void) for (int sex = 0; sex < nSexes; sex++) { nInds[1][sex] = 0; // set count of adults to zero } - inds = juvs; + inds = std::move(juvs); } juvs.clear(); +} +Individual* Population::sampleInd() const { + int index = pRandom->IRandom(0, static_cast(inds.size() - 1)); + return inds[index]; +} + +void Population::sampleIndsWithoutReplacement(string strNbToSample, const set& sampleStages) { + + if (sampledInds.size() > 0) { + sampledInds.clear(); + } + auto rng = pRandom->getRNG(); + vector stagedInds; + + // Stage individuals in eligible stages + for (int stage : sampleStages) { + vector toAdd = getIndividualsInStage(stage); + stagedInds.insert(stagedInds.begin(), toAdd.begin(), toAdd.end()); + } + + if (strNbToSample == "all") { + // Sample all individuals in selected stages + sampledInds = stagedInds; + } + else { // random + int nbToSample = stoi(strNbToSample); + if (stagedInds.size() <= nbToSample) { + // Sample all individuals in selected stages + sampledInds = stagedInds; + } + else { + // Sample n individuals across selected stages + sample(stagedInds.begin(), stagedInds.end(), std::back_inserter(sampledInds), nbToSample, rng); + } + } +} + +int Population::sampleSize() const { + return static_cast(sampledInds.size()); +} + +vector Population::getIndividualsInStage(int stage) { + vector indsInStage; + for (auto ind : inds) { + if (ind->getStats().stage == stage) + indsInStage.push_back(ind); + } + return indsInStage; } // Determine which individuals will disperse void Population::emigration(float localK) { int nsexes; - double disp, Pdisp, NK; - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - emigRules emig = pSpecies->getEmig(); + double disp, pbDisp, NK; + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + emigRules emig = pSpecies->getEmigRules(); emigTraits eparams; - trfrRules trfr = pSpecies->getTrfr(); + transferRules trfr = pSpecies->getTransferRules(); indStats ind; // to avoid division by zero, assume carrying capacity is at least one individual // localK can be zero if there is a moving gradient or stochasticity in K if (localK < 1.0) localK = 1.0; - NK = (float)totalPop() / localK; + NK = static_cast(getNbInds()) / localK; - int ninds = (int)inds.size(); + int ninds = static_cast(inds.size()); // set up local copy of emigration probability table // used when there is no individual variability // NB - IT IS DOUBTFUL THIS CONTRIBUTES ANY SUBSTANTIAL TIME SAVING - if (dem.repType == 0) nsexes = 1; else nsexes = 2; - double Pemig[NSTAGES][NSEXES]; + if (dem.repType == 0) nsexes = 1; + else nsexes = 2; + double pbEmig[gMaxNbStages][gMaxNbSexes]; for (int stg = 0; stg < sstruct.nStages; stg++) { for (int sex = 0; sex < nsexes; sex++) { - if (emig.indVar) Pemig[stg][sex] = 0.0; + if (emig.indVar) pbEmig[stg][sex] = 0.0; else { if (emig.densDep) { if (emig.sexDep) { if (emig.stgDep) { - eparams = pSpecies->getEmigTraits(stg, sex); + eparams = pSpecies->getSpEmigTraits(stg, sex); } else { - eparams = pSpecies->getEmigTraits(0, sex); + eparams = pSpecies->getSpEmigTraits(0, sex); } } else { // !emig.sexDep if (emig.stgDep) { - eparams = pSpecies->getEmigTraits(stg, 0); + eparams = pSpecies->getSpEmigTraits(stg, 0); } else { - eparams = pSpecies->getEmigTraits(0, 0); + eparams = pSpecies->getSpEmigTraits(0, 0); } } - Pemig[stg][sex] = eparams.d0 / (1.0 + exp(-(NK - eparams.beta) * eparams.alpha)); + pbEmig[stg][sex] = eparams.d0 / (1.0 + exp(-(NK - eparams.beta) * eparams.alpha)); } else { // density-independent if (emig.sexDep) { if (emig.stgDep) { - Pemig[stg][sex] = pSpecies->getEmigD0(stg, sex); + pbEmig[stg][sex] = pSpecies->getSpEmigD0(stg, sex); } else { // !emig.stgDep - Pemig[stg][sex] = pSpecies->getEmigD0(0, sex); + pbEmig[stg][sex] = pSpecies->getSpEmigD0(0, sex); } } else { // !emig.sexDep if (emig.stgDep) { - Pemig[stg][sex] = pSpecies->getEmigD0(stg, 0); + pbEmig[stg][sex] = pSpecies->getSpEmigD0(stg, 0); } else { // !emig.stgDep - Pemig[stg][sex] = pSpecies->getEmigD0(0, 0); + pbEmig[stg][sex] = pSpecies->getSpEmigD0(0, 0); } } } @@ -685,25 +926,25 @@ void Population::emigration(float localK) for (int i = 0; i < ninds; i++) { ind = inds[i]->getStats(); - if (ind.status < 1) + if (ind.status < 1) // ToDo: Maybe allow dispersal after translocation? If so, we need to update the pPrevCell and pCurrCell variables of the translocated individuals! { if (emig.indVar) { // individual variability in emigration if (dem.stageStruct && ind.stage != emig.emigStage) { // emigration may not occur - Pdisp = 0.0; + pbDisp = 0.0; } else { // non-structured or individual is in emigration stage - eparams = inds[i]->getEmigTraits(); + eparams = inds[i]->getIndEmigTraits(); if (emig.densDep) { // density-dependent - NK = (float)totalPop() / localK; - Pdisp = eparams.d0 / (1.0 + exp(-(NK - eparams.beta) * eparams.alpha)); + NK = (float)getNbInds() / localK; + pbDisp = eparams.d0 / (1.0 + exp(-(NK - eparams.beta) * eparams.alpha)); } else { // density-independent if (emig.sexDep) { - Pdisp = Pemig[0][ind.sex] + eparams.d0; + pbDisp = pbEmig[0][ind.sex] + eparams.d0; } else { - Pdisp = Pemig[0][0] + eparams.d0; + pbDisp = pbEmig[0][0] + eparams.d0; } } } @@ -713,44 +954,41 @@ void Population::emigration(float localK) if (emig.densDep) { if (emig.sexDep) { if (emig.stgDep) { - Pdisp = Pemig[ind.stage][ind.sex]; + pbDisp = pbEmig[ind.stage][ind.sex]; } else { - Pdisp = Pemig[0][ind.sex]; + pbDisp = pbEmig[0][ind.sex]; } } else { // !emig.sexDep if (emig.stgDep) { - Pdisp = Pemig[ind.stage][0]; + pbDisp = pbEmig[ind.stage][0]; } else { - Pdisp = Pemig[0][0]; + pbDisp = pbEmig[0][0]; } } } else { // density-independent if (emig.sexDep) { if (emig.stgDep) { - Pdisp = Pemig[ind.stage][ind.sex]; + pbDisp = pbEmig[ind.stage][ind.sex]; } else { // !emig.stgDep - Pdisp = Pemig[0][ind.sex]; + pbDisp = pbEmig[0][ind.sex]; } } else { // !emig.sexDep if (emig.stgDep) { - Pdisp = Pemig[ind.stage][0]; + pbDisp = pbEmig[ind.stage][0]; } else { // !emig.stgDep - Pdisp = Pemig[0][0]; + pbDisp = pbEmig[0][0]; } } } - - } // end of no individual variability - - disp = pRandom->Bernoulli(Pdisp); + disp = pRandom->Bernoulli(pbDisp); if (disp == 1) { // emigrant inds[i]->setStatus(1); @@ -767,13 +1005,23 @@ void Population::allEmigrate(void) { } } +// Remove an Individual from the Population +Individual* Population::extractIndividual(int ix) { + Individual* pInd = inds[ix]; + indStats ind = pInd->getStats(); + inds[ix] = nullptr; + nInds[ind.stage][ind.sex]--; + return pInd; +} + // If an Individual has been identified as an emigrant, remove it from the Population disperser Population::extractDisperser(int ix) { - disperser d; + disperser d = disperser(); indStats ind = inds[ix]->getStats(); if (ind.status == 1) { // emigrant - d.pInd = inds[ix]; d.yes = true; - inds[ix] = 0; + d.pInd = inds[ix]; + d.yes = true; + inds[ix] = nullptr; nInds[ind.stage][ind.sex]--; } else { @@ -782,16 +1030,17 @@ disperser Population::extractDisperser(int ix) { return d; } + // For an individual identified as being in the matrix population: // if it is a settler, return its new location and remove it from the current population // otherwise, leave it in the matrix population for possible reporting before deletion disperser Population::extractSettler(int ix) { - disperser d; + disperser d = disperser(); Cell* pCell; indStats ind = inds[ix]->getStats(); - pCell = inds[ix]->getLocn(1); + pCell = inds[ix]->getCurrCell(); d.pInd = inds[ix]; d.pCell = pCell; d.yes = false; if (ind.status == 4 || ind.status == 5) { // settled d.yes = true; @@ -804,300 +1053,25 @@ disperser Population::extractSettler(int ix) { // Add a specified individual to the new/current dispersal group // Add a specified individual to the population void Population::recruit(Individual* pInd) { - inds.push_back(pInd); - indStats ind = pInd->getStats(); + indStats ind = pInd->getStats(); // potentially I need to add localscalings or so? nInds[ind.stage][ind.sex]++; +#ifdef _OPENMP + const std::lock_guard lock(inds_mutex); +#endif // _OPENMP + inds.push_back(pInd); } -//--------------------------------------------------------------------------- - -// Transfer is run for populations in the matrix only -#if RS_RCPP // included also SEASONAL -int Population::transfer(Landscape* pLandscape, short landIx, short nextseason) -#else -int Population::transfer(Landscape* pLandscape, short landIx) -#endif -{ - int ndispersers = 0; - int disperser; - short othersex; - bool mateOK, densdepOK; - intptr patch, popn; - int patchnum; - double localK, popsize, settprob; - Patch* pPatch = 0; - Cell* pCell = 0; - indStats ind; - Population* pNewPopn = 0; - locn newloc, nbrloc; - - landData ppLand = pLandscape->getLandData(); - short reptype = pSpecies->getRepType(); - trfrRules trfr = pSpecies->getTrfr(); - settleType settletype = pSpecies->getSettle(); - settleRules sett; - settleTraits settDD; - settlePatch settle; - simParams sim = paramsSim->getSim(); - - // each individual takes one step - // for dispersal by kernel, this should be the only step taken - int ninds = (int)inds.size(); - for (int i = 0; i < ninds; i++) { - if (trfr.moveModel) { - disperser = inds[i]->moveStep(pLandscape, pSpecies, landIx, sim.absorbing); - } - else { - disperser = inds[i]->moveKernel(pLandscape, pSpecies, reptype, sim.absorbing); - } - ndispersers += disperser; - if (disperser) { - if (reptype > 0) - { // sexual species - record as potential settler in new patch - if (inds[i]->getStatus() == 2) - { // disperser has found a patch - pCell = inds[i]->getLocn(1); - patch = pCell->getPatch(); - if (patch != 0) { // not no-data area - pPatch = (Patch*)patch; - pPatch->incrPossSettler(pSpecies, inds[i]->getSex()); - } - } - } - } - } - -// each individual which has reached a potential patch decides whether to settle - for (int i = 0; i < ninds; i++) { - ind = inds[i]->getStats(); - if (ind.sex == 0) othersex = 1; else othersex = 0; - if (settletype.stgDep) { - if (settletype.sexDep) sett = pSpecies->getSettRules(ind.stage, ind.sex); - else sett = pSpecies->getSettRules(ind.stage, 0); - } - else { - if (settletype.sexDep) sett = pSpecies->getSettRules(0, ind.sex); - else sett = pSpecies->getSettRules(0, 0); - } - if (ind.status == 2) - { // awaiting settlement - pCell = inds[i]->getLocn(1); - if (pCell == 0) { - // this condition can occur in a patch-based model at the time of a dynamic landscape - // change when there is a range restriction in place, since a patch can straddle the - // range restriction and an individual forced to disperse upon patch removal could - // start its trajectory beyond the boundary of the restrictyed range - such a model is - // not good practice, but the condition must be handled by killing the individual conceerned - ind.status = 6; - } - else { - mateOK = false; - if (sett.findMate) { - // determine whether at least one individual of the opposite sex is present in the - // new population - if (matePresent(pCell, othersex)) mateOK = true; - } - else { // no requirement to find a mate - mateOK = true; - } - - densdepOK = false; - settle = inds[i]->getSettPatch(); - if (sett.densDep) - { - patch = pCell->getPatch(); - if (patch != 0) { // not no-data area - pPatch = (Patch*)patch; - if (settle.settleStatus == 0 - || settle.pSettPatch != pPatch) - // note: second condition allows for having moved from one patch to another - // adjacent one - { - // determine whether settlement occurs in the (new) patch - localK = (double)pPatch->getK(); - popn = pPatch->getPopn((intptr)pSpecies); - if (popn == 0) { // population has not been set up in the new patch - popsize = 0.0; - } - else { - pNewPopn = (Population*)popn; - popsize = (double)pNewPopn->totalPop(); - } - if (localK > 0.0) { - // make settlement decision - if (settletype.indVar) settDD = inds[i]->getSettTraits(); -#if RS_RCPP - else settDD = pSpecies->getSettTraits(ind.stage, ind.sex); -#else - else { - if (settletype.sexDep) { - if (settletype.stgDep) - settDD = pSpecies->getSettTraits(ind.stage, ind.sex); - else - settDD = pSpecies->getSettTraits(0, ind.sex); - } - else { - if (settletype.stgDep) - settDD = pSpecies->getSettTraits(ind.stage, 0); - else - settDD = pSpecies->getSettTraits(0, 0); - } - } -#endif //RS_RCPP - settprob = settDD.s0 / - (1.0 + exp(-(popsize / localK - (double)settDD.beta) * (double)settDD.alpha)); - - if (pRandom->Bernoulli(settprob)) { // settlement allowed - densdepOK = true; - settle.settleStatus = 2; - } - else { // settlement procluded - settle.settleStatus = 1; - } - settle.pSettPatch = pPatch; - } - inds[i]->setSettPatch(settle); - } - else { - if (settle.settleStatus == 2) { // previously allowed to settle - densdepOK = true; - } - } - } - } - else { // no density-dependent settlement - densdepOK = true; - settle.settleStatus = 2; - settle.pSettPatch = pPatch; - inds[i]->setSettPatch(settle); - } - - if (mateOK && densdepOK) { // can recruit to patch - ind.status = 4; - ndispersers--; - } - else { // does not recruit - if (trfr.moveModel) { - ind.status = 1; // continue dispersing, unless ... - // ... maximum steps has been exceeded - pathSteps steps = inds[i]->getSteps(); - settleSteps settsteps = pSpecies->getSteps(ind.stage, ind.sex); - if (steps.year >= settsteps.maxStepsYr) { - ind.status = 3; // waits until next year - } - if (steps.total >= settsteps.maxSteps) { - ind.status = 6; // dies - } - } - else { // dispersal kernel - if (sett.wait) { - ind.status = 3; // wait until next dispersal event - } - else { - ind.status = 6; // (dies unless a neighbouring cell is suitable) - } - ndispersers--; - } - } - } - - inds[i]->setStatus(ind.status); - } -#if RS_RCPP - // write each individuals current movement step and status to paths file - if (trfr.moveModel && sim.outPaths) { - if (nextseason >= sim.outStartPaths && nextseason % sim.outIntPaths == 0) { - inds[i]->outMovePath(nextseason); - } - } -#endif - - if (!trfr.moveModel && sett.go2nbrLocn && (ind.status == 3 || ind.status == 6)) - { - // for kernel-based transfer only ... - // determine whether recruitment to a neighbouring cell is possible - - pCell = inds[i]->getLocn(1); - newloc = pCell->getLocn(); - vector nbrlist; - for (int dx = -1; dx < 2; dx++) { - for (int dy = -1; dy < 2; dy++) { - if (dx != 0 || dy != 0) { //cell is not the current cell - nbrloc.x = newloc.x + dx; nbrloc.y = newloc.y + dy; - if (nbrloc.x >= 0 && nbrloc.x <= ppLand.maxX - && nbrloc.y >= 0 && nbrloc.y <= ppLand.maxY) { // within landscape - // add to list of potential neighbouring cells if suitable, etc. - pCell = pLandscape->findCell(nbrloc.x, nbrloc.y); - if (pCell != 0) { // not no-data area - patch = pCell->getPatch(); - if (patch != 0) { // not no-data area - pPatch = (Patch*)patch; - patchnum = pPatch->getPatchNum(); - if (patchnum > 0 && pPatch != inds[i]->getNatalPatch()) - { // not the matrix or natal patch - if (pPatch->getK() > 0.0) - { // suitable - if (sett.findMate) { - if (matePresent(pCell, othersex)) nbrlist.push_back(pCell); - } - else - nbrlist.push_back(pCell); - } - } - } - } - } - } - } - } - int listsize = (int)nbrlist.size(); - if (listsize > 0) { // there is at least one suitable neighbouring cell - if (listsize == 1) { - inds[i]->moveto(nbrlist[0]); - } - else { // select at random from the list - int rrr = pRandom->IRandom(0, listsize - 1); - inds[i]->moveto(nbrlist[rrr]); - } - } - // else list empty - do nothing - individual retains its current location and status - } - } - return ndispersers; -} - -// Determine whether there is a potential mate present in a patch which a potential -// settler has reached -bool Population::matePresent(Cell* pCell, short othersex) -{ - int patch; - Patch* pPatch; - Population* pNewPopn; - int popsize = 0; - bool matefound = false; - - patch = (int)pCell->getPatch(); - if (patch != 0) { - pPatch = (Patch*)pCell->getPatch(); - if (pPatch->getPatchNum() > 0) { // not the matrix patch - if (pPatch->getK() > 0.0) - { // suitable - pNewPopn = (Population*)pPatch->getPopn((intptr)pSpecies); - if (pNewPopn != 0) { - // count members of other sex already resident in the patch - for (int stg = 0; stg < nStages; stg++) { - popsize += pNewPopn->nInds[stg][othersex]; - } - } - if (popsize < 1) { - // add any potential settlers of the other sex - popsize += pPatch->getPossSettlers(pSpecies, othersex); - } - } - } +// Add specified individuals to the population +void Population::recruitMany(std::vector& recruits) { + if (recruits.empty()) return; + for (Individual* pInd : recruits) { + indStats ind = pInd->getStats(); + nInds[ind.stage][ind.sex]++; } - if (popsize > 0) matefound = true; - return matefound; +#ifdef _OPENMP + const std::lock_guard lock(inds_mutex); +#endif // _OPENMP + inds.insert(inds.end(), recruits.begin(), recruits.end()); } //--------------------------------------------------------------------------- @@ -1107,41 +1081,53 @@ bool Population::matePresent(Cell* pCell, short othersex) // FOR MULTIPLE SPECIES, MAY NEED TO SEPARATE OUT THIS IDENTIFICATION STAGE, // SO THAT IT CAN BE PERFORMED FOR ALL SPECIES BEFORE ANY UPDATING OF POPULATIONS -void Population::survival0(float localK, short option0, short option1) +void Population::survival0(float localK, short option0, short option1, std::vector localDemoScaling) { // option0: 0 - stage 0 (juveniles) only // 1 - all stages // 2 - stage 1 and above (all non-juveniles) + // // option1: 0 - development only (when survival is annual) // 1 - development and survival // 2 - survival only (when survival is annual) - densDepParams ddparams = pSpecies->getDensDep(); - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); - // get surrent population size - int ninds = (int)inds.size(); + // get current population size + int ninds = inds.size(); if (ninds == 0) return; // set up local copies of species development and survival tables - int nsexes; - if (dem.repType == 0) nsexes = 1; else nsexes = 2; - float dev[NSTAGES][NSEXES]; - float surv[NSTAGES][NSEXES]; - short minAge[NSTAGES][NSEXES]; + int nsexes = dem.repType == 0 ? 1 : 2; + float dev[gMaxNbStages][gMaxNbSexes]; + float surv[gMaxNbStages][gMaxNbSexes]; + short minAge[gMaxNbStages][gMaxNbSexes]; + for (int stg = 0; stg < sstruct.nStages; stg++) { for (int sex = 0; sex < nsexes; sex++) { if (dem.stageStruct) { if (dem.repType == 1) { // simple sexual model // both sexes use development and survival recorded for females - dev[stg][sex] = pSpecies->getDev(stg, 0); - surv[stg][sex] = pSpecies->getSurv(stg, 0); + if (pSpecies->getDevSpatial() && pSpecies->getDevLayer(stg,0)>=0){ + dev[stg][sex] = pSpecies->getDev(stg,0)*localDemoScaling[pSpecies->getDevLayer(stg,0)]; + } + else dev[stg][sex] = pSpecies->getDev(stg,0); + if (pSpecies->getSurvSpatial() && pSpecies->getSurvLayer(stg,0)>=0){ + surv[stg][sex] = pSpecies->getSurv(stg,0)*localDemoScaling[pSpecies->getSurvLayer(stg,0)]; + } + else surv[stg][sex] = pSpecies->getSurv(stg,0); minAge[stg][sex] = pSpecies->getMinAge(stg, 0); } else { - dev[stg][sex] = pSpecies->getDev(stg, sex); - surv[stg][sex] = pSpecies->getSurv(stg, sex); + if (pSpecies->getDevSpatial() && pSpecies->getDevLayer(stg,sex)>=0){ + dev[stg][sex] = pSpecies->getDev(stg,sex)*localDemoScaling[pSpecies->getDevLayer(stg,sex)]; + } + else dev[stg][sex] = pSpecies->getDev(stg,sex); + if (pSpecies->getSurvSpatial() && pSpecies->getSurvLayer(stg,sex)>=0){ + surv[stg][sex] = pSpecies->getSurv(stg,sex)*localDemoScaling[pSpecies->getSurvLayer(stg,sex)]; + } + else surv[stg][sex] = pSpecies->getSurv(stg,sex); minAge[stg][sex] = pSpecies->getMinAge(stg, sex); } if (option1 == 0) surv[stg][sex] = 1.0; // development only - all survive @@ -1157,14 +1143,11 @@ void Population::survival0(float localK, short option0, short option1) } } } - if (dem.stageStruct) { - // apply density dependence in development and/or survival probabilities for (int stg = 0; stg < nStages; stg++) { for (int sex = 0; sex < nsexes; sex++) { if (option1 != 2 && sstruct.devDens && stg > 0) { // NB DD in development does NOT apply to juveniles, - // which must develop to stage 1 if they survive float effect = 0.0; if (sstruct.devStageDens) { // stage-specific density dependence // NOTE: matrix entries represent effect of ROW on COLUMN @@ -1186,11 +1169,12 @@ void Population::survival0(float localK, short option0, short option1) } } else // not stage-specific - effect = (float)totalPop(); + effect = (float)getNbInds(); if (localK > 0.0) dev[stg][sex] *= exp(-(ddparams.devCoeff * effect) / localK); } // end of if (sstruct.devDens && stg > 0) if (option1 != 0 && sstruct.survDens) { + float effect = 0.0; if (sstruct.survStageDens) { // stage-specific density dependence // NOTE: matrix entries represent effect of ROW on COLUMN @@ -1212,20 +1196,20 @@ void Population::survival0(float localK, short option0, short option1) } } else // not stage-specific - effect = (float)totalPop(); + effect = (float)getNbInds(); if (localK > 0.0) surv[stg][sex] *= exp(-(ddparams.survCoeff * effect) / localK); } // end of if (sstruct.survDens) } } } - // identify which individuals die or develop for (int i = 0; i < ninds; i++) { indStats ind = inds[i]->getStats(); + if ((ind.stage == 0 && option0 < 2) || (ind.stage > 0 && option0 > 0)) { // condition for processing the stage is met... - if (ind.status < 6) { // not already doomed + if (ind.status < 6 || ind.status == 10) { // not already doomed double probsurv = surv[ind.stage][ind.sex]; // does the individual survive? if (pRandom->Bernoulli(probsurv)) { // survives @@ -1234,7 +1218,7 @@ void Population::survival0(float localK, short option0, short option1) if (ind.stage < nStages - 1) { // not final stage if (ind.age >= minAge[ind.stage + 1][ind.sex]) { // old enough to enter next stage if (pRandom->Bernoulli(probdev)) { - inds[i]->developing(); + inds[i]->setToDevelop(); } } } @@ -1250,13 +1234,15 @@ void Population::survival0(float localK, short option0, short option1) // Apply survival changes to the population void Population::survival1(void) { - int ninds = (int)inds.size(); + for (int i = 0; i < ninds; i++) { indStats ind = inds[i]->getStats(); - if (ind.status > 5) { // doomed to die + + if (ind.status > 5 && ind.status != 10) { // doomed to die; status 10 is translocated? + if (ind.status != 10) //not going into cold storage -> is there a new status 10 in this new_genetics version?? delete inds[i]; - inds[i] = NULL; + inds[i] = nullptr; nInds[ind.stage][ind.sex]--; } else { @@ -1267,14 +1253,12 @@ void Population::survival1(void) } } } - -// remove pointers to dead individuals clean(); } void Population::ageIncrement(void) { int ninds = (int)inds.size(); - stageParams sstruct = pSpecies->getStage(); + stageParams sstruct = pSpecies->getStageParams(); for (int i = 0; i < ninds; i++) { inds[i]->ageIncrement(sstruct.maxAge); } @@ -1286,38 +1270,32 @@ void Population::clean(void) { int ninds = (int)inds.size(); if (ninds > 0) { - // ALTERNATIVE METHOD: AVOIDS SLOW SORTING OF POPULATION - std::vector survivors; // all surviving individuals - for (int i = 0; i < ninds; i++) { - if (inds[i] != NULL) { - survivors.push_back(inds[i]); - } - } - inds.clear(); - inds = survivors; + inds.erase(std::remove(inds.begin(), inds.end(), (Individual *)NULL), inds.end()); #if RS_RCPP shuffle(inds.begin(), inds.end(), pRandom->getRNG()); #else -#if !RSDEBUG - // do not randomise individuals in RSDEBUG mode, as the function uses rand() +#ifdef NDEBUG + // do not randomise individuals in DEBUG mode, as the function uses rand() // and therefore the randomisation will differ between identical runs of RS shuffle(inds.begin(), inds.end(), pRandom->getRNG()); -#endif // !RSDEBUG +#endif // NDEBUG #endif // RS_RCPP } } //--------------------------------------------------------------------------- -// Open population file and write header record -bool Population::outPopHeaders(int landNr, bool patchModel) { +// Close population file +bool Population::outPopFinishLandscape() { + if (outPop.is_open()) outPop.close(); + outPop.clear(); + return true; +} - if (landNr == -999) { // close file - if (outPop.is_open()) outPop.close(); - outPop.clear(); - return true; - } +//--------------------------------------------------------------------------- +// Open population file and write header record +bool Population::outPopStartLandscape(int landNr, bool patchModel) { string name; simParams sim = paramsSim->getSim(); @@ -1325,16 +1303,19 @@ bool Population::outPopHeaders(int landNr, bool patchModel) { // NEED TO REPLACE CONDITIONAL COLUMNS BASED ON ATTRIBUTES OF ONE SPECIES TO COVER // ATTRIBUTES OF *ALL* SPECIES AS DETECTED AT MODEL LEVEL - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + name = paramsSim->getDir(2) + + (sim.batchMode ? "Batch" + to_string(sim.batchNum) + "_" : "") + + "Sim" + to_string(sim.simulation) + "_Land" + to_string(landNr) + "_Pop.txt"; if (sim.batchMode) { name = paramsSim->getDir(2) - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) + "_Land" + Int2Str(landNr) + "_Pop.txt"; + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + to_string(landNr) + "_Pop.txt"; } else { - name = paramsSim->getDir(2) + "Sim" + Int2Str(sim.simulation) + "_Pop.txt"; + name = paramsSim->getDir(2) + "Sim" + to_string(sim.simulation) + "_Pop.txt"; } outPop.open(name.c_str()); outPop << "Rep\tYear\tRepSeason"; @@ -1362,7 +1343,6 @@ bool Population::outPopHeaders(int landNr, bool patchModel) { if (dem.repType != 0) outPop << "\tNfemales\tNmales"; } outPop << endl; - return outPop.is_open(); } @@ -1372,12 +1352,11 @@ void Population::outPopulation(int rep, int yr, int gen, float eps, bool patchModel, bool writeEnv, bool gradK) { Cell* pCell; + // NEED TO REPLACE CONDITIONAL COLUMNS BASED ON ATTRIBUTES OF ONE SPECIES TO COVER -// ATTRIBUTES OF *ALL* SPECIES AS DETECTED AT MODEL LEVEL - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - popStats p; + demogrParams dem = pSpecies->getDemogrParams(); + popStats p; outPop << rep << "\t" << yr << "\t" << gen; if (patchModel) { outPop << "\t" << pPatch->getPatchNum(); @@ -1401,7 +1380,7 @@ void Population::outPopulation(int rep, int yr, int gen, float eps, } outPop << "\t" << pSpecies->getSpNum(); if (dem.stageStruct) { - p = getStats(); + p = getStats(pPatch->getDemoScaling()); outPop << "\t" << p.nNonJuvs; // non-juvenile stage totals from permanent array for (int stg = 1; stg < nStages; stg++) { @@ -1415,7 +1394,7 @@ void Population::outPopulation(int rep, int yr, int gen, float eps, } } else { // non-structured population - outPop << "\t" << totalPop(); + outPop << "\t" << getNbInds(); if (dem.repType != 0) { // sexual model outPop << "\t" << nInds[1][0] << "\t" << nInds[1][1]; @@ -1427,47 +1406,44 @@ void Population::outPopulation(int rep, int yr, int gen, float eps, //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- -// Open individuals file and write header record -void Population::outIndsHeaders(int rep, int landNr, bool patchModel) +// Close individuals file +void Population::outIndsFinishReplicate() { - - if (landNr == -999) { // close file - if (outInds.is_open()) { - outInds.close(); outInds.clear(); - } - return; + if (outInds.is_open()) { + outInds.close(); outInds.clear(); } + return; +} +//--------------------------------------------------------------------------- +// Open individuals file and write header record +void Population::outIndsStartReplicate(int rep, int landNr, bool patchModel) +{ string name; - demogrParams dem = pSpecies->getDemogr(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); simParams sim = paramsSim->getSim(); - if (sim.batchMode) { name = paramsSim->getDir(2) - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) - + "_Land" + Int2Str(landNr) + "_Rep" + Int2Str(rep) + "_Inds.txt"; - } - else { - name = paramsSim->getDir(2) + "Sim" + Int2Str(sim.simulation) - + "_Rep" + Int2Str(rep) + "_Inds.txt"; - } - outInds.open(name.c_str()); + + (sim.batchMode ? "Batch" + to_string(sim.batchNum) + "_" : "") + + "Sim" + to_string(sim.simulation) + + "_Land" + to_string(landNr) + "_Rep" + to_string(rep) + "_Inds.txt"; + outInds.open(name.c_str()); outInds << "Rep\tYear\tRepSeason\tSpecies\tIndID\tStatus"; if (patchModel) outInds << "\tNatal_patch\tPatchID"; else outInds << "\tNatal_X\tNatal_Y\tX\tY"; if (dem.repType != 0) outInds << "\tSex"; if (dem.stageStruct) outInds << "\tAge\tStage"; + if (pSpecies->getNbGenLoadTraits() > 0) outInds << "\tProbViable"; if (emig.indVar) { if (emig.densDep) outInds << "\tD0\tAlpha\tBeta"; else outInds << "\tEP"; } if (trfr.indVar) { - if (trfr.moveModel) { + if (trfr.usesMovtProc) { if (trfr.moveType == 1) { // SMS outInds << "\tDP\tGB\tAlphaDB\tBetaDB"; } @@ -1484,11 +1460,10 @@ void Population::outIndsHeaders(int rep, int landNr, bool patchModel) outInds << "\tS0\tAlphaS\tBetaS"; } outInds << "\tDistMoved"; -#if RSDEBUG - // ALWAYS WRITE NO. OF STEPS +#ifndef NDEBUG outInds << "\tNsteps"; #else - if (trfr.moveModel) outInds << "\tNsteps"; + if (trfr.usesMovtProc) outInds << "\tNsteps"; #endif outInds << endl; } @@ -1498,20 +1473,17 @@ void Population::outIndsHeaders(int rep, int landNr, bool patchModel) void Population::outIndividual(Landscape* pLandscape, int rep, int yr, int gen, int patchNum) { - //int x, y, p_id; bool writeInd; pathSteps steps; Cell* pCell; - landParams ppLand = pLandscape->getLandParams(); - demogrParams dem = pSpecies->getDemogr(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); short spNum = pSpecies->getSpNum(); int ninds = (int)inds.size(); - for (int i = 0; i < ninds; i++) { indStats ind = inds[i]->getStats(); if (yr == -1) { // write all initialised individuals @@ -1537,11 +1509,11 @@ void Population::outIndividual(Landscape* pLandscape, int rep, int yr, int gen, else { // non-structured population outInds << "\t" << ind.status; } - pCell = inds[i]->getLocn(1); + pCell = inds[i]->getCurrCell(); locn loc; if (pCell == 0) loc.x = loc.y = -1; // beyond boundary or in no-data cell else loc = pCell->getLocn(); - pCell = inds[i]->getLocn(0); + pCell = inds[i]->getPrevCell(); locn natalloc = pCell->getLocn(); if (ppLand.patchModel) { outInds << "\t" << inds[i]->getNatalPatch()->getPatchNum(); @@ -1555,8 +1527,10 @@ void Population::outIndividual(Landscape* pLandscape, int rep, int yr, int gen, if (dem.repType != 0) outInds << "\t" << ind.sex; if (dem.stageStruct) outInds << "\t" << ind.age << "\t" << ind.stage; + if (pSpecies->getNbGenLoadTraits() > 0) outInds << "\t" << inds[i]->getGeneticFitness(); + if (emig.indVar) { - emigTraits e = inds[i]->getEmigTraits(); + emigTraits e = inds[i]->getIndEmigTraits(); if (emig.densDep) { outInds << "\t" << e.d0 << "\t" << e.alpha << "\t" << e.beta; } @@ -1564,21 +1538,20 @@ void Population::outIndividual(Landscape* pLandscape, int rep, int yr, int gen, outInds << "\t" << e.d0; } } // end of if (emig.indVar) - if (trfr.indVar) { - if (trfr.moveModel) { + if (trfr.usesMovtProc) { if (trfr.moveType == 1) { // SMS - trfrSMSTraits s = inds[i]->getSMSTraits(); + trfrSMSTraits s = inds[i]->getIndSMSTraits(); outInds << "\t" << s.dp << "\t" << s.gb; outInds << "\t" << s.alphaDB << "\t" << s.betaDB; } // end of SMS if (trfr.moveType == 2) { // CRW - trfrCRWTraits c = inds[i]->getCRWTraits(); + trfrCRWTraits c = inds[i]->getIndCRWTraits(); outInds << "\t" << c.stepLength << "\t" << c.rho; } // end of CRW } else { // kernel - trfrKernTraits k = inds[i]->getKernTraits(); + trfrKernelParams k = inds[i]->getIndKernTraits(); if (trfr.twinKern) { outInds << "\t" << k.meanDist1 << "\t" << k.meanDist2 << "\t" << k.probKern1; @@ -1590,7 +1563,7 @@ void Population::outIndividual(Landscape* pLandscape, int rep, int yr, int gen, } if (sett.indVar) { - settleTraits s = inds[i]->getSettTraits(); + settleTraits s = inds[i]->getIndSettTraits(); outInds << "\t" << s.s0 << "\t" << s.alpha << "\t" << s.beta; } @@ -1601,73 +1574,244 @@ void Population::outIndividual(Landscape* pLandscape, int rep, int yr, int gen, + (natalloc.y - loc.y) * (natalloc.y - loc.y))); outInds << "\t" << d; } -#if RSDEBUG +#ifndef NDEBUG // ALWAYS WRITE NO. OF STEPS steps = inds[i]->getSteps(); outInds << "\t" << steps.year; #else - if (trfr.moveModel) { + if (trfr.usesMovtProc) { steps = inds[i]->getSteps(); outInds << "\t" << steps.year; } #endif outInds << endl; } // end of writeInd condition + } } -//--------------------------------------------------------------------------- +void Population::outputGeneValues(ofstream& ofsGenes, const int& yr, const int& gen) const { -//--------------------------------------------------------------------------- -// Write records to genetics file -void Population::outGenetics(const int rep, const int year, const int landNr) -{ + const bool isDiploid = pSpecies->isDiploid(); + int indID; + float alleleOnChromA, alleleOnChromB; + float domCoefA, domCoefB; - simParams sim = paramsSim->getSim(); + // Subset traits that are selected to be output + set traitTypes = pSpecies->getTraitTypes(); + set outputTraitTypes; + for (auto trType : traitTypes) { + if (pSpecies->getSpTrait(trType)->isOutput()) + outputTraitTypes.insert(trType); +} - if (landNr >= 0) { // open file - Genome* pGenome; - genomeData gen = pSpecies->getGenomeData(); - if (gen.trait1Chromosome) { - pGenome = new Genome(pSpecies->getNChromosomes(), pSpecies->getNLoci(0), - pSpecies->isDiploid()); - } - else { - pGenome = new Genome(pSpecies); + // Fetch map to positions for each trait + // Presumably faster than fetching for every individual + map> allGenePositions; + for (auto trType : outputTraitTypes) { + set traitPositions = pSpecies->getSpTrait(trType)->getGenePositions(); + allGenePositions.insert(make_pair(trType, traitPositions)); } - pGenome->outGenHeaders(rep, landNr, sim.outGenXtab); - delete pGenome; - return; - } - if (landNr == -999) { // close file - Genome* pGenome = new Genome(); - pGenome->outGenHeaders(rep, landNr, sim.outGenXtab); - delete pGenome; - return; + set positions; + for (Individual* ind : sampledInds) { + indID = ind->getId(); + for (auto trType : outputTraitTypes) { + positions = allGenePositions[trType]; + auto indTrait = ind->getTrait(trType); + for (auto pos : positions) { + alleleOnChromA = indTrait->getAlleleValueAtLocus(0, pos); + if (trType == GENETIC_LOAD1 || trType == GENETIC_LOAD2 || trType == GENETIC_LOAD3 || trType == GENETIC_LOAD4 || trType == GENETIC_LOAD5) { + domCoefA = indTrait->getDomCoefAtLocus(0, pos); } - - short spNum = pSpecies->getSpNum(); - short nstages = 1; - if (pSpecies->stageStructured()) { - stageParams sstruct = pSpecies->getStage(); - nstages = sstruct.nStages; + else { + domCoefA = 0.0; + } + ofsGenes << yr << '\t' << gen << '\t' << indID << '\t' << to_string(trType) << '\t' << pos << '\t' << alleleOnChromA << '\t' << domCoefA; + if (isDiploid) { + alleleOnChromB = indTrait->getAlleleValueAtLocus(1, pos); + if (trType == GENETIC_LOAD1 || trType == GENETIC_LOAD2 || trType == GENETIC_LOAD3 || trType == GENETIC_LOAD4 || trType == GENETIC_LOAD5) { + domCoefB = indTrait->getDomCoefAtLocus(1, pos); +} + else { + domCoefB = 0.0; + } + ofsGenes << '\t' << alleleOnChromB << '\t' << domCoefB; + } + ofsGenes << endl; + } + } } +} +// --------------------------------------------------------------------------- +// Extract all individuals of a population with certain characteristics based on age, stage and sex +// returns a set of pointers to the individuals +// --------------------------------------------------------------------------- +std::vector Population::getIndsWithCharacteristics( // Select a set of individuals with specified characteristics + int min_age, // min age (0 if not set) + int max_age, // max age (max age if not set) + int stage, // stage + int sex //sex +){ + // get all suitable individuals based on settings + std::vector filteredInds; int ninds = (int)inds.size(); +#if RS_RCPP + Rcpp::Rcout << "Number individuals in cell: " << ninds << endl; +#endif + if (ninds > 0) { + // copy ALL individuals to filteredInds for (int i = 0; i < ninds; i++) { - indStats ind = inds[i]->getStats(); - if (year == 0 || sim.outGenType == 1 - || (sim.outGenType == 0 && ind.stage == 0) - || (sim.outGenType == 2 && ind.stage == nstages - 1)) { - inds[i]->outGenetics(rep, year, spNum, landNr, sim.outGenXtab); + filteredInds.push_back(inds[i]); } - } + // check status of inividuals + for (int i = 0; i < ninds; i++) { + if (inds[i] != NULL && inds[i]->getStats().status != 0 && inds[i]->getStats().status != 4 && inds[i]->getStats().status != 5){ // only accept individuals with status 0, 4 or 5 (not in transfer phase + not dead + not already translocated) + // Rcpp::Rcout << "Status: " << inds[i]->getStats().status << endl; + filteredInds[i] = NULL; // set it to NULL + } + } + + // Check minimal age + if (min_age!=-9){ + // loop over all number of individuals in cell + for (int i = 0; i < ninds; i++) { + if (filteredInds[i] != NULL && inds[i]->getStats().age < min_age){ // if not already NULL + age too young + filteredInds[i] = NULL; // set it to NULL } - + } + } + // check max age + if (max_age!=-9){ + // loop over all number of individuals in cell + for (int i = 0; i < ninds; i++) { + if (filteredInds[i] != NULL && inds[i]->getStats().age < max_age){// if not already NULL + age too old + if (filteredInds[i] != NULL) filteredInds[i] = NULL; // set it to NULL if not already NULL + } + } + } + // check stage + if (stage!=-9){ + // loop over all number of individuals in cell + for (int i = 0; i < ninds; i++) { + if (filteredInds[i] != NULL && inds[i]->getStats().stage != stage){// if not already NULL + stage not correct + if (filteredInds[i] != NULL) filteredInds[i] = NULL; // set it to NULL if not already NULL + } + } + } + // check sex + if (sex!=-9){ + // loop over all number of individuals in cell + for (int i = 0; i < ninds; i++) { + if (filteredInds[i] != NULL && inds[i]->getStats().sex != sex){// if not already NULL + sex not correct + if (filteredInds[i] != NULL) filteredInds[i] = NULL; // set it to NULL if not already NULL + } + } + } + } else { +#if RS_RCPP + Rcpp::Rcout << "No individuals in source patch" << endl; +#endif + return filteredInds; + } + int nfiltered = 0; + for ( auto filtered : filteredInds){ + if (filtered != NULL) nfiltered++; + } + + // loop over iterator of filteredInds and remove NULL values + filteredInds.erase(std::remove(filteredInds.begin(), filteredInds.end(), nullptr), filteredInds.end()); + + return filteredInds; +}; //--------------------------------------------------------------------------- +// Clean the sampled individuals //--------------------------------------------------------------------------- +void Population::cleanSampledInds(Individual* pInd // Return a set of individuals with specified characteristics +){ + // find inds[j] and remove it from sampledInds + sampledInds.erase(std::remove(sampledInds.begin(), sampledInds.end(), pInd), sampledInds.end()); +}; +//--------------------------------------------------------------------------- +// Sample N individuals from the population with a given set of characteristics +// --------------------------------------------------------------------------- +int Population::sampleIndividuals( // Select a set of individuals with specified characteristics +// void Population::sampleIndividuals( // Select a set of individuals with specified characteristics + int nb, // number of individuals to sample + int min_age, // min age (0 if not set) + int max_age, // max age (max age if not set) + int stage, // stage + int sex //sex + ){ + if(sampledInds.size() > 0) sampledInds.clear(); // clear old vector + auto rng = pRandom->getRNG(); // random number for sampling from suitable individuals + + // get individuals with the characteristics + std::vector filtered; + filtered = getIndsWithCharacteristics(min_age, max_age, stage, sex); +#if RS_RCPP + Rcpp::Rcout << "Number of individuals with fitting characteristics: " << filtered.size() << endl; +#endif + if (filtered.size() <= nb) + // Sample all individuals in selected stages + sampledInds = filtered; + else { + vector out; + // Sample n individuals across filtered individuals + std::sample(filtered.begin(), filtered.end(), std::back_inserter(out), nb, rng); + std::copy(out.begin(), out.end(), std::inserter(sampledInds, sampledInds.end())); + } + + int nb_sampled = 0; + if (sampledInds.size() > 0) { + for (int i = 0; i < (int)sampledInds.size(); i++) { + if (sampledInds[i] != NULL) nb_sampled++; + } + } + return nb_sampled; +} +// --------------------------------------------------------------------------- +// catch individuals according to catching rate +// --------------------------------------------------------------------------- +Individual* Population::catchIndividual( // Translocate a set of individuals with specified characteristics + double catching_rate, + int j +){ + Individual* catched; + int id = inds[j]->getId(); + // If individual is part of the sampledInds vector: + if (std::find(sampledInds.begin(), sampledInds.end(), inds[j]) != std::end(sampledInds)){ + // try to catch individual +#if RS_RCPP + if(catching_rate > 1) Rcpp::Rcout << "Catching rate: " << catching_rate << std::endl; +#endif + if (pRandom->Bernoulli(catching_rate)){ + indStats indstat = inds[j]->getStats(); + catched = inds[j]; + // remove individual from source patch + inds[j] = 0; + nInds[indstat.stage][indstat.sex]--; + cleanSampledInds(catched); // clean vector of sampled individuals after the event + return catched; + }else { + cleanSampledInds(inds[j]); // clean vector of sampled individuals after the event + return NULL; + } + } else { + return NULL; + } +} + +// --------------------------------------------------------------------------- +bool Population::getSizeSampledInds( +){ + bool size = false; + if (sampledInds.size() > 0) size = true; + return size; +}; + //--------------------------------------------------------------------------- diff --git a/Population.h b/Population.h index fd1fa66..df1db74 100644 --- a/Population.h +++ b/Population.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -34,14 +34,14 @@ The matrix Population(s) hold(s) Individuals which are currently in the process of transfer through the matrix. For full details of RangeShifter, please see: -Bocedi G., Palmer S.C.F., Peer G., Heikkinen R.K., Matsinos Y.G., Watts K. +Bocedi G., Palmer S.C.F., Pe’er G., Heikkinen R.K., Matsinos Y.G., Watts K. and Travis J.M.J. (2014). RangeShifter: a platform for modelling spatial -eco-evolutionary dynamics and species responses to environmental changes. +eco-evolutionary dynamics and species’ responses to environmental changes. Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 Authors: Greta Bocedi & Steve Palmer, University of Aberdeen -Last updated: 22 January 2022 by Steve Palmer + Last updated: 25 June 2021 by Steve Palmer ------------------------------------------------------------------------------*/ @@ -58,6 +58,12 @@ using namespace std; #include "Landscape.h" #include "Patch.h" #include "Cell.h" +#include "NeutralStatsManager.h" + +#ifdef _OPENMP +#include +#include +#endif //--------------------------------------------------------------------------- @@ -67,38 +73,43 @@ struct popStats { struct disperser { Individual *pInd; Cell *pCell; bool yes; }; +struct zombie { + Individual* pInd; +}; struct traitsums { // sums of trait genes for dispersal - int ninds[NSEXES]; // no. of individuals - double sumD0[NSEXES]; // sum of maximum emigration probability - double ssqD0[NSEXES]; // sum of squares of maximum emigration probability - double sumAlpha[NSEXES]; // sum of slope of emigration dens-dep reaction norm - double ssqAlpha[NSEXES]; // sum of squares of slope of emigration den-dep reaction norm - double sumBeta[NSEXES]; // sum of inflection point of emigration reaction norm - double ssqBeta[NSEXES]; // sum of squares of inflection point of emigration reaction norm - double sumDist1[NSEXES]; // sum of kernel I mean - double ssqDist1[NSEXES]; // sum of squares of kernel I mean - double sumDist2[NSEXES]; // sum of kernel II mean - double ssqDist2[NSEXES]; // sum of squares of kernel II mean - double sumProp1[NSEXES]; // sum of propn using kernel I - double ssqProp1[NSEXES]; // sum of squares of propn using kernel I - double sumDP[NSEXES]; // sum of SMS directional persistence - double ssqDP[NSEXES]; // sum of squares of SMS directional persistence - double sumGB[NSEXES]; // sum of SMS goal bias - double ssqGB[NSEXES]; // sum of squares of SMS goal bias - double sumAlphaDB[NSEXES]; // sum of SMS dispersal bias decay rate - double ssqAlphaDB[NSEXES]; // sum of squares of SMS dispersal bias decay rate - double sumBetaDB[NSEXES]; // sum of SMS dispersal bias decay infl. pt. - double ssqBetaDB[NSEXES]; // sum of squares of SMS dispersal bias decay infl. pt. - double sumStepL[NSEXES]; // sum of CRW step length - double ssqStepL[NSEXES]; // sum of squares of CRW step length - double sumRho[NSEXES]; // sum of CRW correlation coefficient - double ssqRho[NSEXES]; // sum of squares of CRW correlation coefficient - double sumS0[NSEXES]; // sum of maximum settlement probability - double ssqS0[NSEXES]; // sum of squares of maximum settlement probability - double sumAlphaS[NSEXES]; // sum of slope of settlement den-dep reaction norm - double ssqAlphaS[NSEXES]; // sum of squares of slope of settlement den-dep reaction norm - double sumBetaS[NSEXES]; // sum of inflection point of settlement reaction norm - double ssqBetaS[NSEXES]; // sum of squares of inflection point of settlement reaction norm + int ninds[gMaxNbSexes]; // no. of individuals + double sumD0[gMaxNbSexes]; // sum of maximum emigration probability + double ssqD0[gMaxNbSexes]; // sum of squares of maximum emigration probability + double sumAlpha[gMaxNbSexes]; // sum of slope of emigration dens-dep reaction norm + double ssqAlpha[gMaxNbSexes]; // sum of squares of slope of emigration den-dep reaction norm + double sumBeta[gMaxNbSexes]; // sum of inflection point of emigration reaction norm + double ssqBeta[gMaxNbSexes]; // sum of squares of inflection point of emigration reaction norm + double sumDist1[gMaxNbSexes]; // sum of kernel I mean + double ssqDist1[gMaxNbSexes]; // sum of squares of kernel I mean + double sumDist2[gMaxNbSexes]; // sum of kernel II mean + double ssqDist2[gMaxNbSexes]; // sum of squares of kernel II mean + double sumProp1[gMaxNbSexes]; // sum of propn using kernel I + double ssqProp1[gMaxNbSexes]; // sum of squares of propn using kernel I + double sumDP[gMaxNbSexes]; // sum of SMS directional persistence + double ssqDP[gMaxNbSexes]; // sum of squares of SMS directional persistence + double sumGB[gMaxNbSexes]; // sum of SMS goal bias + double ssqGB[gMaxNbSexes]; // sum of squares of SMS goal bias + double sumAlphaDB[gMaxNbSexes]; // sum of SMS dispersal bias decay rate + double ssqAlphaDB[gMaxNbSexes]; // sum of squares of SMS dispersal bias decay rate + double sumBetaDB[gMaxNbSexes]; // sum of SMS dispersal bias decay infl. pt. + double ssqBetaDB[gMaxNbSexes]; // sum of squares of SMS dispersal bias decay infl. pt. + double sumStepL[gMaxNbSexes]; // sum of CRW step length + double ssqStepL[gMaxNbSexes]; // sum of squares of CRW step length + double sumRho[gMaxNbSexes]; // sum of CRW correlation coefficient + double ssqRho[gMaxNbSexes]; // sum of squares of CRW correlation coefficient + double sumS0[gMaxNbSexes]; // sum of maximum settlement probability + double ssqS0[gMaxNbSexes]; // sum of squares of maximum settlement probability + double sumAlphaS[gMaxNbSexes]; // sum of slope of settlement den-dep reaction norm + double ssqAlphaS[gMaxNbSexes]; // sum of squares of slope of settlement den-dep reaction norm + double sumBetaS[gMaxNbSexes]; // sum of inflection point of settlement reaction norm + double ssqBetaS[gMaxNbSexes]; // sum of squares of inflection point of settlement reaction norm + double sumGeneticFitness[gMaxNbSexes]; + double ssqGeneticFitness[gMaxNbSexes]; }; class Population { @@ -112,19 +123,20 @@ class Population { int // Landscape resolution ); ~Population(void); - traitsums getTraits(Species*); - popStats getStats(void); - Species* getSpecies(void); - int getNInds(void); - int totalPop(void); - int stagePop( // return no. of Individuals in a specified stage - int // stage + traitsums getIndTraitsSums(Species*); + popStats getStats( + std::vector ); + Species* getSpecies(void); + int getNbInds() const; + int getNbInds(int stg) const ; + int getNbInds(int stg, int sex) const; void extirpate(void); // Remove all individuals void reproduction( const float, // local carrying capacity const float, // effect of environmental gradient and/or stochasticty - const int // Landscape resolution + const int, // Landscape resolution + std::vector // local demographic scaling ); // Following reproduction of ALL species, add juveniles to the population void fledge(void); @@ -132,6 +144,10 @@ class Population { float // local carrying capacity ); void allEmigrate(void); // All individuals emigrate after patch destruction + // Remove an individual from the Population + Individual* extractIndividual( + int // index no. to the Individual in the inds vector + ); // If an individual has been identified as an emigrant, remove it from the Population disperser extractDisperser( int // index no. to the Individual in the inds vector @@ -145,30 +161,14 @@ class Population { void recruit( // Add a specified individual to the population Individual* // pointer to Individual ); -#if RS_RCPP - int transfer( // Executed for the Population(s) in the matrix only - Landscape*, // pointer to Landscape - short, // landscape change index - short // year + Individual* sampleInd() const; + void sampleIndsWithoutReplacement(string n, const set& sampleStages); + int sampleSize() const; + vector getIndividualsInStage(int stage); + void recruitMany( // Add specified individuals to the population + std::vector& // vector of pointers to Individuals ); - // Determine whether there is a potential mate present in a patch which a potential - // settler has reached - bool matePresent( - Cell*, // pointer to the Cell which the potential settler has reached - short // sex of the required mate (0 = female, 1 = male) - ); -#else - int transfer( // Executed for the Population(s) in the matrix only - Landscape*, // pointer to Landscape - short // landscape change index - ); - // Determine whether there is a potential mate present in a patch which a potential - // settler has reached - bool matePresent( - Cell*, // pointer to the Cell which the potential settler has reached - short // sex of the required mate (0 = female, 1 = male) - ); -#endif // RS_RCPP + // Determine survival and development and record in individual's status code // Changes are NOT applied to the Population at this stage void survival0( @@ -176,14 +176,16 @@ class Population { short, // option0: 0 - stage 0 (juveniles) only // 1 - all stages // 2 - stage 1 and above (all non-juveniles) - short // option1: 0 - development only (when survival is annual) + short, // option1: 0 - development only (when survival is annual) // 1 - development and survival // 2 - survival only (when survival is annual) + std::vector // local demographic scaling ); void survival1(void); // Apply survival changes to the population void ageIncrement(void); - bool outPopHeaders( // Open population file and write header record - int, // Landscape number (-999 to close the file) + bool outPopFinishLandscape(); // Close population file + bool outPopStartLandscape( // Open population file and write header record + int, // Landscape number bool // TRUE for a patch-based model, FALSE for a cell-based model ); void outPopulation( // Write record to population file @@ -196,9 +198,10 @@ class Population { bool // TRUE if there is a gradient in carrying capacity ); - void outIndsHeaders( // Open individuals file and write header record + void outIndsFinishReplicate(); // Close individuals file + void outIndsStartReplicate( // Open individuals file and write header record int, // replicate - int, // Landscape number (-999 to close the file) + int, // Landscape number bool // TRUE for a patch-based model, FALSE for a cell-based model ); void outIndividual( // Write records to individuals file @@ -208,24 +211,79 @@ class Population { int, // generation int // Patch number ); - void outGenetics( // Write records to genetics file - const int, // replicate - const int, // year - const int // landscape number - ); + void outputGeneValues(ofstream& ofsGenes, const int& yr, const int& gen) const; void clean(void); // Remove zero pointers to dead or dispersed individuals + void updatePopNeutralTables(); + double getAlleleFrequency(int locus, int allele); + int getAlleleTally(int locus, int allele); + int getHeteroTally(int locus, int allele); + int countHeterozygoteLoci(); + vector countNbHeterozygotesEachLocus(); + double computeHs(); + std::vector getIndsWithCharacteristics( // Return a set of individuals with specified characteristics + int, // min age + int, // max age + int, // stage + int //sex + ); + void cleanSampledInds( + Individual* // individual to remove from sampled individuals vector + ); // clean sampled individuals vector + + int sampleIndividuals( // Select a set of individuals with specified characteristics; return the number of individuals with those characteristics + // void sampleIndividuals( // Select a set of individuals with specified characteristics; return the number of individuals with those characteristics + int, //number of individuals to sample + int, // min age (0 if not set) + int, // max age (max age if not set) + int, // stage + int //sex + ); + + Individual* catchIndividual( + double, // catching rate + int + ); + + // void completeTranslocation( + // std::vector // catched individuals + // ); + + // void recruitTranslocated( + // Individual* + // ); + + bool getSizeSampledInds( + ); + +#ifdef UNIT_TESTS + // Testing only + void clearInds() { inds.clear(); } // empty inds vector to avoid deallocating individual is used separately in test + void shuffleInds() { shuffle(inds.begin(), inds.end(), pRandom->getRNG()); } +#endif // UNIT_TESTS + private: short nStages; short nSexes; Species *pSpecies; // pointer to the species Patch *pPatch; // pointer to the patch - int nInds[NSTAGES][NSEXES]; // no. of individuals in each stage/sex +#ifdef _OPENMP + std::atomic nInds[gMaxNbStages][gMaxNbSexes]; // no. of individuals in each stage/sex +#else + int nInds[gMaxNbStages][gMaxNbSexes]; // no. of individuals in each stage/sex +#endif // _OPENMP - std::vector inds; // all individuals in population except ... - std::vector juvs; // ... juveniles until reproduction of ALL species + vector inds; // all individuals in population except ... + vector juvs; // ... juveniles until reproduction of ALL species // has been completed + vector sampledInds; + //std::vector sampledInds; // individuals with specified characteristics from translocation!!! + vector popNeutralCountTables; + void resetPopNeutralTables(); +#ifdef _OPENMP + std::mutex inds_mutex; +#endif // _OPENMP }; //--------------------------------------------------------------------------- @@ -236,10 +294,6 @@ extern paramInit *paramsInit; extern paramSim *paramsSim; extern RSrandom *pRandom; -#if RSDEBUG -extern ofstream DEBUGLOG; -#endif - //--------------------------------------------------------------------------- #endif diff --git a/QuantitativeTrait.h b/QuantitativeTrait.h new file mode 100644 index 0000000..3ffe997 --- /dev/null +++ b/QuantitativeTrait.h @@ -0,0 +1,53 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ + +#ifndef TRAITTH +#define TRAITTH + +#include +#include +#include + +#include "Parameters.h" +#include "Allele.h" +#include "SpeciesTrait.h" + +using namespace std; + +// Base interface for all genetic traits +class QuantitativeTrait { +public: + virtual void mutate() = 0; + virtual unique_ptr clone() const = 0; //copies parameters (if not static) not gene seqeunces + virtual void inheritGenes(const bool&, QuantitativeTrait*, set const& , int ) = 0; + virtual int getNLoci() const = 0; + virtual float getMutationRate() const = 0; + virtual bool isInherited() const = 0; + virtual float getAlleleValueAtLocus(short chromosome, int i) const = 0; + virtual float getDomCoefAtLocus(short whichChromosome, int position) const = 0; + virtual float express() = 0; + virtual ~QuantitativeTrait() { } +}; + +extern RSrandom* pRandom; + +#endif diff --git a/README.md b/README.md index 832f354..bd266e6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # RangeShifter core code -This repo contains the core simulation code for RangeShifter v2.0 and is not meant to be compiled or run on its own. +This repo contains the core simulation code for RangeShifter v3.0 and is not meant to be compiled or run on its own. It is used as a shared codebase for multiple interfaces. - + If you are only interested in using RangeShifter, you can ignore this and head to the repo of one of the interfaces: @@ -14,7 +14,19 @@ If you are only interested in using RangeShifter, you can ignore this and head t ## Usage: git subtree -In order to ensure that the same version of RangeShifter's core code is used by all three interfaces (RangeShiftR, RangeShifter-batch and the GUI), each interface repo keeps a copy of RScore as a git subtree. In this section, we describe how to use the git subtrees to update the subfolder and copy this repo anew. +In order to ensure that the same version of RangeShifter's core code is used by all three interfaces (RangeShiftR, RangeShifter-batch and the GUI), each interface repo keeps a copy of RScore as a git subtree. + +In this section, we describe how to use the git subtrees to update the subfolder and copy this repo anew. + +⚠️Please note: + +Modifying files inside the RScore subtree directly in the interface repositories can lead to merge conflicts or broken history, if you do not strictly follow the workflow presented here. + +You must ensure that you always push changes on files located in the RScore directory to the git subtree repository RScore using `git subtree push` and must **not** push them to the RangeShiftR-pkg or RangeShifter_batch repository. + +The most robust way to avoid merge conflicts and broken history is to modify files directly inside the RScore repository and then pull the changes into the interface repositories by using `git subtree pull`. + +### Set up First, in a local clone of one of the interface repos, add a remote named `RScore` pointing to the RScore repo. This will be convenient as a shortcut for git subtree commands. @@ -44,8 +56,20 @@ while for RangeShiftR, use: git subtree pull --prefix RangeShiftR/src/RScore RScore main ``` +#### Dealing with conflicts + +If conflicts occur, the RScore repository should be treated as the source of truth: + +``` +git checkout --theirs +git add +git commit +``` + ### Pushing new changes to RScore +If changes were made inside the subtree directory (not recommended, but sometimes unavoidable), they must be pushed back to this repository **before** pushing to the interface repository: + ```bash git subtree push --prefix RScore ``` @@ -56,6 +80,8 @@ e.g., from RangeShifter-batch's `main` to RScore's `main`: git subtree push --prefix src/RScore RScore main ``` +Afterwards, update other interface repositories using `git subtree pull`. + ### Switching the subfolder to a new branch There is unfortunately to do so. To track a different branch of RScore, one must delete the RScore subfolder (via git) and import the subtree again: @@ -72,5 +98,4 @@ See [CONTRIBUTING](https://github.com/RangeShifter/RScore/blob/main/CONTRIBUTING ## Maintainers -- [@JetteReeg](https://github.com/JetteReeg) -- [@TheoPannetier](https://github.com/TheoPannetier) +- [\@JetteReeg](https://github.com/JetteReeg) diff --git a/RSrandom.cpp b/RSrandom.cpp index 8f638d7..c284400 100644 --- a/RSrandom.cpp +++ b/RSrandom.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -19,245 +19,219 @@ * --------------------------------------------------------------------------*/ - #include "RSrandom.h" - -//--------------- 2.) New version of RSrandom.cpp - -#if !RS_RCPP - -#if RSDEBUG +#ifdef _OPENMP +#include +#endif // _OPENMP +#ifndef NDEBUG #include "Parameters.h" extern paramSim* paramsSim; -// ofstream RSRANDOMLOG; #endif +#if RS_RCPP +std::uint32_t RS_random_seed = 0; +#else int RS_random_seed = 0; +#endif // C'tor -RSrandom::RSrandom() +#if RS_RCPP +// if parameter seed is negative, a random seed will be generated, else it is used as seed +RSrandom::RSrandom(std::int64_t seed) { -#if RSDEBUG - // fixed seed - RS_random_seed = 666; -#else - // random seed -#if LINUX_CLUSTER - std::random_device device; - RS_random_seed = device(); // old versions of g++ on Windows return a constant value within a given Windows - // session; in this case better use time stamp + // get seed + std::vector random_seed(3); + random_seed[0] = 1967593562; + random_seed[1] = 3271254416; + if (seed < 0) { + // random seed +#if RSWIN64 + random_seed[2] = std::time(NULL) + (seed * (-17)); #else - RS_random_seed = std::time(NULL); + std::random_device device; + random_seed[2] = device(); #endif -#endif // RSDEBUG - -#if BATCH && RSDEBUG - DEBUGLOG << "RSrandom::RSrandom(): RS_random_seed=" << RS_random_seed << endl; -#endif // RSDEBUG + } + else { + // fixed seed + random_seed[2] = seed; + } - // set up Mersenne Twister RNG - gen = new mt19937(RS_random_seed); + RS_random_seed = random_seed[2]; - // Set up standard uniform distribution - pRandom01 = new uniform_real_distribution(0.0, 1.0); - // Set up standard normal distribution - pNormal = new normal_distribution(0.0, 1.0); -} + // set up Mersenne Twister random number generator with seed sequence + std::seed_seq seq(random_seed.begin(), random_seed.end()); -RSrandom::~RSrandom(void) -{ - delete gen; - if(pRandom01 != 0) - delete pRandom01; - if(pNormal != 0) - delete pNormal; -} +#ifdef _OPENMP + int nb_generators = omp_get_max_threads(); + gens.reserve(nb_generators); + for (int i = 0; i < nb_generators; i++) + gens.emplace_back(seq); +#else + gens.reserve(1); + gens.emplace_back(seq); +#endif // _OPENMP -mt19937 RSrandom::getRNG(void) -{ - return *gen; -} -double RSrandom::Random(void) -{ - // return random number between 0 and 1 - return pRandom01->operator()(*gen); + // Set up standard uniform distribution + pRandom01 = new uniform_real_distribution(0.0, 1.0); + // Set up standard normal distribution + pNormal = new normal_distribution(0.0, 1.0); } +#else +RSrandom::RSrandom() { -int RSrandom::IRandom(int min, int max) -{ - // return random integer in the interval min <= x <= max - uniform_int_distribution unif(min, max); - return unif(*gen); +#ifndef NDEBUG + // fixed seed + RS_random_seed = 11011; +#else + // random seed +#if LINUX_CLUSTER + std::random_device device; + RS_random_seed = device(); // old versions of g++ on Windows return a constant value within a given Windows + // session; in this case better use time stamp +#else + RS_random_seed = std::time(NULL); +#endif +#endif // NDEBUG + + // set up Mersenne Twister RNG +#ifdef _OPENMP + int nb_generators = omp_get_max_threads(); + gens.reserve(nb_generators); + for (int i = 0; i < nb_generators; i++) + gens.emplace_back(RS_random_seed + i); +#else + gens.reserve(1); + gens.emplace_back(RS_random_seed); +#endif // _OPENMP + + // Set up standard uniform distribution + pRandom01 = new uniform_real_distribution(0.0, 1.0); + // Set up standard normal distribution + pNormal = new normal_distribution(0.0, 1.0); } +#endif // RS_RCPP -int RSrandom::Bernoulli(double p) -{ - if (p < 0) throw runtime_error("Bernoulli's p cannot be negative.\n"); - if (p > 1) throw runtime_error("Bernoulli's p cannot be above 1.\n"); - return Random() < p; +RSrandom::~RSrandom(void) { + gens.clear(); + if (pRandom01 != 0) + delete pRandom01; + if (pNormal != 0) + delete pNormal; } -double RSrandom::Normal(double mean, double sd) -{ - return mean + sd * pNormal->operator()(*gen); +mt19937 RSrandom::getRNG() { +#ifdef _OPENMP + return gens[omp_get_thread_num() % gens.size()]; +#else + return gens[0]; +#endif // _OPENMP } -int RSrandom::Poisson(double mean) -{ - poisson_distribution poiss(mean); - return poiss(*gen); +double RSrandom::Random() { + // return random number between 0 and 1 +#ifdef _OPENMP + return pRandom01->operator()(gens[omp_get_thread_num() % gens.size()]); +#else + return pRandom01->operator()(gens[0]); +#endif // _OPENMP } +int RSrandom::IRandom(int min, int max) { + // return random integer in the interval min <= x <= max + if (min == max) + return min; -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - -#else // if RS_RCPP - -//--------------- 3.) R package version of RSrandom.cpp - - #if RSDEBUG - #include "Parameters.h" - extern paramSim *paramsSim; - //ofstream RSRANDOMLOG; - #endif - - std::uint32_t RS_random_seed = 0; - - // C'tor - // if parameter seed is negative, a random seed will be generated, else it is used as seed - RSrandom::RSrandom(std::int64_t seed) - { - // get seed - std::vector random_seed(3); - random_seed[0] = 1967593562; - random_seed[1] = 3271254416; - if (seed < 0) { - // random seed - #if RSWIN64 - random_seed[2] = std::time(NULL) + ( seed * (-17) ); - #else - std::random_device device; - random_seed[2] = device(); - #endif - #if BATCH && RSDEBUG - DEBUGLOG << "RSrandom::RSrandom(): Generated random seed = "; - #endif - } - else{ - // fixed seed - random_seed[2] = seed; - #if BATCH && RSDEBUG - DEBUGLOG << "RSrandom::RSrandom(): Use fixed seed = "; - #endif - } - - RS_random_seed = random_seed[2]; - #if BATCH && RSDEBUG - DEBUGLOG << RS_random_seed << endl; - #endif - - // set up Mersenne Twister random number generator with seed sequence - std::seed_seq seq(random_seed.begin(),random_seed.end()); - gen = new mt19937(seq); - - // Set up standard uniform distribution - pRandom01 = new uniform_real_distribution (0.0,1.0); - // Set up standard normal distribution - pNormal = new normal_distribution (0.0,1.0); - } - - RSrandom::~RSrandom(void) { - delete gen; - if (pRandom01 != 0) delete pRandom01; - if (pNormal != 0) delete pNormal; - } - - mt19937 RSrandom::getRNG(void) { - // return random number generator - return *gen; - } - - double RSrandom::Random(void) { - // return random number between 0 and 1 - return pRandom01->operator()(*gen); - } + uniform_int_distribution unif(min, max); +#ifdef _OPENMP + return unif(gens[omp_get_thread_num() % gens.size()]); +#else + return unif(gens[0]); +#endif // _OPENMP +} - int RSrandom::IRandom(int min,int max) { - // return random integer in the interval min <= x <= max - uniform_int_distribution unif(min,max); - return unif(*gen); - } +float RSrandom::FRandom(float min, float max) { + if (min == max) return min; + // return random double in the interval min <= x <= max + uniform_real_distribution unif(min, max); - int RSrandom::Bernoulli(double p) { - if (p < 0) throw runtime_error("Bernoulli's p cannot be negative.\n"); - if (p > 1) throw runtime_error("Bernoulli's p cannot be above 1.\n"); - return Random() < p; - } +#ifdef _OPENMP + return unif(gens[omp_get_thread_num() % gens.size()]); +#else + return unif(gens[0]); +#endif +} - double RSrandom::Normal(double mean,double sd) { - return mean + sd * pNormal->operator()(*gen); +int RSrandom::Bernoulli(double p) { + if (p < 0) { + throw runtime_error("Bernoulli's p cannot be negative.\n"); } - - int RSrandom::Poisson(double mean) { - poisson_distribution poiss(mean); - return poiss(*gen); + if (p > 1) { + throw runtime_error("Bernoulli's p cannot be above 1.\n"); } + return Random() < p; +} +int RSrandom::Binomial(const int& n, const double& p) { + binomial_distribution binom(n, p); +#ifdef _OPENMP + return binom(gens[omp_get_thread_num() % gens.size()]); +#else + return binom(gens[0]); +#endif +} - /* ADDITIONAL DISTRIBUTIONS +double RSrandom::Normal(double mean, double sd) { +#ifdef _OPENMP + return mean + sd * pNormal->operator()(gens[omp_get_thread_num() % gens.size()]); +#else + return mean + sd * pNormal->operator()(gens[0]); +#endif // _OPENMP +} - // Beta distribution - sample from two gamma distributions - double RSrandom::Beta(double p0,double p1) { - double g0,g1,beta; - if (p0 > 0.0 && p1 > 0.0) { // valid beta parameters - gamma_distribution gamma0(p0,1.0); - gamma_distribution gamma1(p1,1.0); - g0 = gamma0(*gen); - g1 = gamma1(*gen); - beta = g0 / (g0 + g1); - } - else { // return invalid value - beta = -666.0; - } - return beta; - } +int RSrandom::Poisson(double mean) { + poisson_distribution poiss(mean); +#ifdef _OPENMP + return poiss(gens[omp_get_thread_num() % gens.size()]); +#else + return poiss(gens[0]); +#endif // _OPENMP +} - // Gamma distribution - double RSrandom::Gamma(double p0,double p1) { // using shape (=p0) and scale (=p1) - double p2,gamma; - if (p0 > 0.0 && p1 > 0.0) { // valid gamma parameters - p2 = 1.0 / p1; - gamma_distribution gamma0(p0,p2); // using shape/alpha (=p0) and rate/beta (=p2=1/p1) - gamma = gamma0(*gen); - } - else { // return invalid value - gamma = -666.0; - } - return gamma; - } +double RSrandom::Gamma(double shape, double scale) { //scale = mean/shape, shape must be positive and scale can be positive or negative - // Cauchy distribution - double RSrandom::Cauchy(double loc, double scale) { - double res; - if (scale > 0.0) { // valid scale parameter - cauchy_distribution cauchy(loc,scale); - res = cauchy(*gen); - } - else { // return invalid value - res = -666.0; - } - return res; - } + gamma_distribution<> gamma(shape, abs(scale)); +#ifdef _OPENMP + double x = poiss(gens[omp_get_thread_num() % gens.size()]); +#else + double x = gamma(gens[0]); +#endif // _OPENMP + if (scale < 0) x = -x; + return x; +} - */ +double RSrandom::NegExp(double mean) { + double r1 = 0.0000001 + this->Random() * (1.0 - 0.0000001); + double x = (-1.0 * mean) * log(r1); + return x; +} -#endif // RS_RCPP +void RSrandom::fixNewSeed(int seed) { +#ifdef _OPENMP + for (int i = 0; i < omp_get_max_threads(); i++) + gens[i].seed(seed + i); +#else + gens[0].seed(seed); +#endif // _OPENMP +} +//-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- -#if RSDEBUG && !RS_RCPP +#ifdef UNIT_TESTS +#if !RS_RCPP void testRSrandom() { { @@ -279,5 +253,6 @@ int RSrandom::Poisson(double mean) assert(bern_trial == 0 || bern_trial == 1); } } -#endif // RSDEBUG +#endif +#endif // UNIT_TESTS //--------------------------------------------------------------------------- diff --git a/RSrandom.h b/RSrandom.h index 9767245..7ed9e34 100644 --- a/RSrandom.h +++ b/RSrandom.h @@ -1,37 +1,37 @@ /*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * * This file is part of RangeShifter. - * + * * RangeShifter 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. - * + * * RangeShifter 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 RangeShifter. If not, see . - * + * --------------------------------------------------------------------------*/ - - -/*------------------------------------------------------------------------------ -RangeShifter v2.0 RSrandom -Implements the RSrandom class + /*------------------------------------------------------------------------------ -Authors: Steve Palmer, University of Aberdeen - Anne-Kathleen Malchow, Potsdam University + RangeShifter v2.0 RSrandom -Last updated: 12 January 2021 by Steve Palmer + Implements the RSrandom class -------------------------------------------------------------------------------*/ + Authors: Steve Palmer, University of Aberdeen + Anne-Kathleen Malchow, Potsdam University + + Last updated: 12 January 2021 by Steve Palmer + + ------------------------------------------------------------------------------*/ #ifndef RSrandomH #define RSrandomH @@ -39,89 +39,55 @@ Last updated: 12 January 2021 by Steve Palmer #include #include #include +#include +#include +#include #include "Utils.h" +#if !LINUX_CLUSTER +#include +#endif using namespace std; -#if RSDEBUG -extern ofstream DEBUGLOG; +#if RS_RCPP +typedef uint32_t seed_t; +#else +typedef int seed_t; #endif +class RSrandom +{ + +public: #if !RS_RCPP -//--------------- 2.) New version of RSrandom.cpp - #include - #include - #if !LINUX_CLUSTER - #include - #endif - - class RSrandom - { - - public: - RSrandom(void); - ~RSrandom(void); - double Random(void); - int IRandom(int, int); - int Bernoulli(double); - double Normal(double, double); - int Poisson(double); - mt19937 getRNG(void); + RSrandom(void); +#else + RSrandom(std::int64_t); // if int is negative, a random seed will be generated, else it is used as seed +#endif + ~RSrandom(void); + double Random(void); + int IRandom(int, int); + float FRandom(float, float); + int Bernoulli(double); + int Binomial(const int& n, const double& p); + double Normal(double, double); + double Gamma(double, double); + double NegExp(double); + int Poisson(double); + mt19937 getRNG(void); + void fixNewSeed(int); + seed_t getSeed() const { return RS_random_seed; }; private: - mt19937* gen; + seed_t RS_random_seed; + std::vector gens; std::uniform_real_distribution<>* pRandom01; std::normal_distribution<>* pNormal; }; - -//-------------------------------------------------------------------------------------------------- -//-------------------------------------------------------------------------------------------------- - - - -//--------------- 3.) R package version of RSrandom.cpp - - -#else // if RS_RCPP - - - #include - #include - #if RSWIN64 - #include - #endif - - class RSrandom { - - public: - RSrandom(std::int64_t); // if int is negative, a random seed will be generated, else it is used as seed - ~RSrandom(void); - mt19937 getRNG(void); - double Random(void); - int IRandom(int,int); - int Bernoulli(double); - double Normal(double,double); - int Poisson(double); - /* ADDITIONAL DISTRIBUTIONS - double Beta(double,double); - double Gamma(double,double); // !! make sure correct definition is used: using shape and scale (as defined here) OR using shape/alpha and rate/beta (=1/scale) - double Cauchy(double,double); - */ - - private: - mt19937 *gen; - std::uniform_real_distribution<> *pRandom01; - std::normal_distribution<> *pNormal; - }; - - - -#endif // !RS_RCPP - -#if RSDEBUG +#ifdef UNIT_TESTS void testRSrandom(); -#endif // RSDEBUG +#endif // UNIT_TESTS //--------------------------------------------------------------------------- diff --git a/RandomCheck.cpp b/RandomCheck.cpp deleted file mode 100644 index e18aeaa..0000000 --- a/RandomCheck.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/*---------------------------------------------------------------------------- - * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell - * - * This file is part of RangeShifter. - * - * RangeShifter 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. - * - * RangeShifter 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 RangeShifter. If not, see . - * - --------------------------------------------------------------------------*/ - - -//--------------------------------------------------------------------------- - -#include "RandomCheck.h" -//--------------------------------------------------------------------------- - -ifstream inRandom; -ofstream outRandom; -ofstream outBernoulli; -ofstream outNormal; -ofstream outPoisson; -ofstream outIRandom; - -void randomCheck(void) -{ - -int samplesize,irandMin,irandMax; -double bernMean,normMean,normSD,poisMean; -string name,header; -simParams sim = paramsSim->getSim(); - - name = paramsSim->getDir(1) + "RandomCheck.txt"; - inRandom.open(name.c_str()); - if (!inRandom.is_open()) { - #if !RS_RCPP - cout << endl << "***** Error opening input file RandomCheck.txt" << endl; - #endif - inRandom.clear(); - return; - } - for (int i = 0; i < 7; i++) { - inRandom >> header; - } - inRandom >> samplesize >> bernMean >> normMean >> normSD >> poisMean >> irandMin >> irandMax; - - name = paramsSim->getDir(2) + "Random.txt"; - outRandom.open(name.c_str()); - name = paramsSim->getDir(2) + "Bernoulli.txt"; - outBernoulli.open(name.c_str()); - name = paramsSim->getDir(2) + "Normal.txt"; - outNormal.open(name.c_str()); - name = paramsSim->getDir(2) + "Poisson.txt"; - outPoisson.open(name.c_str()); - name = paramsSim->getDir(2) + "IRandom.txt"; - outIRandom.open(name.c_str()); - - for (int i = 0; i < samplesize; i++) { - outRandom << pRandom->Random() << endl; - outBernoulli << pRandom->Bernoulli(bernMean) << endl; - outNormal << pRandom->Normal(normMean,normSD) << endl; - outPoisson << pRandom->Poisson(poisMean) << endl; - outIRandom << pRandom->IRandom(irandMin,irandMax) << endl; - } - - inRandom.close(); - inRandom.clear(); - outRandom.close(); - outRandom.clear(); - outBernoulli.close(); - outBernoulli.clear(); - outNormal.close(); - outNormal.clear(); - outPoisson.close(); - outPoisson.clear(); - outIRandom.close(); - outIRandom.clear(); - -} - -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- diff --git a/Species.cpp b/Species.cpp index d91a3e4..2311efd 100644 --- a/Species.cpp +++ b/Species.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -25,115 +25,120 @@ #include "Species.h" //--------------------------------------------------------------------------- -Species::Species(void) +Species::Species(const short& repro, const short& nbRepSeasons, const bool& hasStgStruct, const short& nStg, const bool& usesMovtProc, const short& movementType) : + repType{repro}, + repSeasons{nbRepSeasons}, + stageStruct{hasStgStruct}, + nStages{nStg}, + usesMovtProcess{usesMovtProc}, + moveType{movementType} { // initialise demographic parameters - repType = 0; nStages = 2; - stageStruct = false; - propMales = 0.5; harem = 1.0; bc = 1.0; lambda = 1.5; probRep = 1.0; - repSeasons = 1; - repInterval = 0; maxAge = 1000; survival = 1; - fecDens = false; fecStageDens = false; - devDens = false; devStageDens = false; - survDens = false; survStageDens = false; + propMales = 0.0; + harem = 1.0; + bc = 1.0; + lambda = 1.5; + probRep = 1.0; + repInterval = 0; + maxAge = 1000; + survival = 1; + fecDens = false; + fecStageDens = false; + devDens = false; + devStageDens = false; + survDens = false; + survStageDens = false; + fecSpatial = false; + survSpatial = false; + devSpatial = false; disperseOnLoss = false; - for (int i = 0; i < NSTAGES; i++) { - for (int j = 0; j < NSEXES; j++) { - fec[i][j] = 0.0; dev[i][j] = 0.0; surv[i][j] = 0.0; + for (int i = 0; i < gMaxNbStages; i++) { + for (int j = 0; j < gMaxNbSexes; j++) { + fec[i][j] = 0.0; + dev[i][j] = 0.0; + surv[i][j] = 0.0; minAge[i][j] = 0; + fecLayer[i][j] = -9; + devLayer[i][j] = -9; + survLayer[i][j] = -9; } } devCoeff = survCoeff = 1.0; - ddwtFec = ddwtDev = ddwtSurv = 0; ddwtFecDim = ddwtDevDim = ddwtSurvDim = 0; - habK = 0; habDimK = 0; - minRK = 1.0; maxRK = 2.0; - - // initialise genome attributes - nChromosomes = nTraits = 0; - emigTrait[0] = 0; emigTrait[1] = 0; - movtTrait[0] = 0; movtTrait[1] = 0; - settTrait[0] = 0; settTrait[1] = 0; - diploid = true; - neutralMarkers = false; - pleiotropic = false; - trait1Chromosome = true; - probMutn = 0.0001f; - probCrossover = 0.0001f; - alleleSD = mutationSD = 0.1f; - nNLoci = 0; - nLoci = NULL; - traitdata = NULL; - traitnames = NULL; - nTraitNames = 0; + ddwtFec = ddwtDev = ddwtSurv = 0; + ddwtFecDim = ddwtDevDim = ddwtSurvDim = 0; + habK = 0; + habDimK = 0; + minRK = 1.0; + maxRK = 2.0; // initialise emigration parameters - densDepEmig = false; stgDepEmig = false; sexDepEmig = false; indVarEmig = false; + densDepEmig = false; + stgDepEmig = false; + sexDepEmig = false; + indVarEmig = false; emigStage = 0; - for (int i = 0; i < NSTAGES; i++) { - for (int j = 0; j < NSEXES; j++) { - d0[i][j] = 0.0; alphaEmig[i][j] = 0.0; betaEmig[i][j] = 1.0; + for (int i = 0; i < gMaxNbStages; i++) { + for (int j = 0; j < gMaxNbSexes; j++) { + d0[i][j] = 0.0; + alphaEmig[i][j] = 0.0; + betaEmig[i][j] = 1.0; } } - for (int j = 0; j < NSEXES; j++) { - d0Mean[0][j] = 0.0; alphaMean[0][j] = 0.0; betaMean[0][j] = 1.0; - d0SD[0][j] = 0.0; alphaSD[0][j] = 0.0; betaSD[0][j] = 1.0; - } - d0Scale = alphaScale = betaScale = 0.0; - // initialise transfer parameters - moveModel = false; stgDepTrfr = false; sexDepTrfr = false; distMort = false; + stgDepTrfr = false; + sexDepTrfr = false; + distMort = false; indVarTrfr = false; twinKern = false; habMort = false; costMap = false; - moveType = 1; - for (int i = 0; i < NSTAGES; i++) { - for (int j = 0; j < NSEXES; j++) { - meanDist1[i][j] = 100.0f; meanDist2[i][j] = 1000.0f; probKern1[i][j] = 0.99f; - } - } - for (int j = 0; j < NSEXES; j++) { - dist1Mean[0][j] = 100.0; dist1SD[0][j] = 10.0; - dist2Mean[0][j] = 1000.0; dist2SD[0][j] = 100.0; - PKern1Mean[0][j] = 0.9f; PKern1SD[0][j] = 0.01f; - stepLgthMean[0][j] = 10.0; stepLgthSD[0][j] = 1.0; - rhoMean[0][j] = 0.9f; rhoSD[0][j] = 0.01f; - dpMean[0][j] = 1.0; dpSD[0][j] = 0.1f; - gbMean[0][j] = 1.0; gbSD[0][j] = 0.1f; - alphaDBMean[0][j] = 1.0; alphaDBSD[0][j] = 0.1f; - betaDBMean[0][j] = 10.0; betaDBSD[0][j] = 1.0; - } - pr = 1; prMethod = 1; memSize = 1; goalType = 0; - dp = 1.0; gb = 1.0; alphaDB = 1.0; betaDB = 100000; - stepMort = 0.0; stepLength = 10.0; rho = 0.9f; - habStepMort = 0; habCost = 0; - fixedMort = 0.0; mortAlpha = 0.0; mortBeta = 1.0; - dist1Scale = dist2Scale = PKern1Scale = stepLScale = rhoScale = 0.0; - dpScale = 0.1f; gbScale = 0.1f; alphaDBScale = 0.1f; betaDBScale = 1.0; + for (int i = 0; i < gMaxNbStages; i++) { + for (int j = 0; j < gMaxNbSexes; j++) { + meanDist1[i][j] = 100.0f; + meanDist2[i][j] = 1000.0f; + probKern1[i][j] = 0.99f; + } + } + pr = 1; + prMethod = 1; + memSize = 1; + goalType = 0; + dp = 1.0; + gb = 1.0; + alphaDB = 1.0; + betaDB = 100000; + stepMort = 0.0; + stepLength = 10.0; + rho = 0.9f; + habStepMort = 0; + habCost = 0; + fixedMort = 0.0; + mortAlpha = 0.0; + mortBeta = 1.0; habDimTrfr = 0; - straigtenPath = false; + straightenPath = false; fullKernel = false; - // initialise settlement parameters - stgDepSett = false; sexDepSett = false; indVarSett = false; - - for (int i = 0; i < NSTAGES; i++) { - for (int j = 0; j < NSEXES; j++) { - densDepSett[i][j] = false; wait[i][j] = false; go2nbrLocn[i][j] = false; findMate[i][j] = false; - maxStepsYr[i][j] = 99999999; minSteps[i][j] = 0; maxSteps[i][j] = 99999999; - s0[i][j] = 1.0; alphaS[i][j] = 0.0; betaS[i][j] = 1.0; - } - } - for (int j = 0; j < NSEXES; j++) { - alphaSMean[0][j] = 0.0; alphaSSD[0][j] = 0.0; - betaSMean[0][j] = 0.0; betaSSD[0][j] = 0.0; - s0Mean[0][j] = 0.0; s0SD[0][j] = 0.0; - } - alphaSScale = 0.0; betaSScale = 0.0; s0Scale = 0.0; - - // initialise attributes + stgDepSett = false; + sexDepSett = false; + indVarSett = false; + for (int i = 0; i < gMaxNbStages; i++) { + for (int j = 0; j < gMaxNbSexes; j++) { + densDepSett[i][j] = false; + wait[i][j] = false; + go2nbrLocn[i][j] = false; + findMate[i][j] = false; + maxStepsYr[i][j] = 99999999; + minSteps[i][j] = 0; + maxSteps[i][j] = 99999999; + s0[i][j] = 1.0; + alphaS[i][j] = 0.0; + betaS[i][j] = 1.0; + } + } + // initialise attribute defaults spNum = 0; - + resetGeneticParameters(); } Species::~Species() { @@ -144,9 +149,6 @@ Species::~Species() { if (ddwtSurv != 0) deleteDDwtSurv(); // transfer parameters if (habCost != 0 || habStepMort != 0) deleteHabCostMort(); - if (nLoci != NULL) deleteLoci(); - if (traitdata != NULL) deleteTraitData(); - if (traitnames != NULL) deleteTraitNames(); } short Species::getSpNum(void) { return spNum; } @@ -157,6 +159,8 @@ short Species::getSpNum(void) { return spNum; } void Species::setDemogr(const demogrParams d) { if (d.repType >= 0 && d.repType <= 2) repType = d.repType; + if (d.repType == 1 || d.repType == 2) diploid = true; + else diploid = false; if (d.repSeasons >= 1) repSeasons = d.repSeasons; stageStruct = d.stageStruct; if (d.propMales > 0.0 && d.propMales < 1.0) propMales = d.propMales; @@ -165,7 +169,7 @@ void Species::setDemogr(const demogrParams d) { if (d.lambda > 0.0) lambda = d.lambda; } -demogrParams Species::getDemogr(void) { +demogrParams Species::getDemogrParams(void) { demogrParams d; d.repType = repType; d.repSeasons = repSeasons; @@ -222,13 +226,16 @@ void Species::setStage(const stageParams s) { if (s.maxAge >= 1) maxAge = s.maxAge; if (s.survival >= 0 && s.survival <= 2) survival = s.survival; if (s.probRep > 0.0 && s.probRep <= 1.0) probRep = s.probRep; - fecDens = s.fecDens; fecStageDens = s.fecStageDens; - devDens = s.devDens; devStageDens = s.devStageDens; - survDens = s.survDens; survStageDens = s.survStageDens; + fecDens = s.fecDens; + fecStageDens = s.fecStageDens; + devDens = s.devDens; + devStageDens = s.devStageDens; + survDens = s.survDens; + survStageDens = s.survStageDens; disperseOnLoss = s.disperseOnLoss; } -stageParams Species::getStage(void) { +stageParams Species::getStageParams(void) { stageParams s; s.nStages = nStages; s.repInterval = repInterval; s.maxAge = maxAge; s.survival = survival; s.probRep = probRep; @@ -241,12 +248,12 @@ stageParams Species::getStage(void) { void Species::setFec(short stg, short sex, float f) { // NB fecundity for stage 0 must always be zero - if (stg > 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES && f >= 0) + if (stg > 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes && f >= 0) fec[stg][sex] = f; } float Species::getFec(short stg, short sex) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) return fec[stg][sex]; else return 0.0; } @@ -254,7 +261,7 @@ float Species::getFec(short stg, short sex) { float Species::getMaxFec(void) { float maxfec = 0.0; if (stageStruct) { - for (int stg = 1; stg < NSTAGES; stg++) { + for (int stg = 1; stg < gMaxNbStages; stg++) { if (fec[stg][0] > maxfec) maxfec = fec[stg][0]; } } @@ -263,35 +270,80 @@ float Species::getMaxFec(void) { } void Species::setDev(short stg, short sex, float d) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES && d >= 0) + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes && d >= 0) dev[stg][sex] = d; } float Species::getDev(short stg, short sex) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) return dev[stg][sex]; else return 0.0; } void Species::setSurv(short stg, short sex, float s) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES && s >= 0) + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes && s >= 0) surv[stg][sex] = s; } float Species::getSurv(short stg, short sex) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) return surv[stg][sex]; else return 0.0; } +void Species::setFecSpatial(bool spat) { + fecSpatial = spat; +} + +void Species::setDevSpatial(bool spat) { + devSpatial = spat; +} + +void Species::setSurvSpatial(bool spat) { + survSpatial = spat; +} + +void Species::setFecLayer(short stg,short sex,short l) { +if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes && l >= 0) + fecLayer[stg][sex] = l; +} + +short Species::getFecLayer(short stg,short sex) { +if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) + return fecLayer[stg][sex]; +else return -9; +} + +void Species::setDevLayer(short stg,short sex,short l) { +if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes && l >= 0) + devLayer[stg][sex] = l; +} + +short Species::getDevLayer(short stg,short sex) { +if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) + return devLayer[stg][sex]; +else return -9; +} + +void Species::setSurvLayer(short stg,short sex,short l) { +if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes && l >= 0) + survLayer[stg][sex] = l; +} + +short Species::getSurvLayer(short stg,short sex) { +if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) + return survLayer[stg][sex]; +else return -9; +} + void Species::setMinAge(short stg, short sex, int age) { // NB min age for stages 0 & 1 must always be zero - if (stg > 1 && stg < NSTAGES && sex >= 0 && sex < NSEXES && age >= 0) + if (stg > 1 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes && age >= 0) minAge[stg][sex] = age; } short Species::getMinAge(short stg, short sex) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) return minAge[stg][sex]; else return 0; } @@ -308,7 +360,7 @@ densDepParams Species::getDensDep(void) { } void Species::createDDwtFec(short mSize) { - if (mSize >= 0 && mSize < (NSTAGES * NSEXES)) { + if (mSize >= 0 && mSize < (gMaxNbStages * gMaxNbSexes)) { if (ddwtFec != 0) deleteDDwtFec(); ddwtFecDim = mSize; ddwtFec = new float* [mSize]; @@ -340,7 +392,7 @@ void Species::deleteDDwtFec(void) { } void Species::createDDwtDev(short mSize) { - if (mSize >= 0 && mSize < (NSTAGES * NSEXES)) { + if (mSize >= 0 && mSize < (gMaxNbStages * gMaxNbSexes)) { if (ddwtDev != 0) deleteDDwtDev(); ddwtDevDim = mSize; ddwtDev = new float* [mSize]; @@ -372,7 +424,7 @@ void Species::deleteDDwtDev(void) { } void Species::createDDwtSurv(short mSize) { - if (mSize >= 0 && mSize < (NSTAGES * NSEXES)) { + if (mSize >= 0 && mSize < (gMaxNbStages * gMaxNbSexes)) { if (ddwtSurv != 0) deleteDDwtSurv(); ddwtSurvDim = mSize; ddwtSurv = new float* [mSize]; @@ -402,8 +454,7 @@ void Species::deleteDDwtSurv(void) { delete[] ddwtSurv; ddwtSurv = 0; } } - -// Functions to handle min/max R or K (under environmental stochasticity) +//void Species::setMinMax(float min,float max) { void Species::setMinMax(float min, float max) { if (min >= 0.0 && max > min) { minRK = min; maxRK = max; @@ -416,501 +467,148 @@ float Species::getMinMax(short opt) { //--------------------------------------------------------------------------- -// Genome functions - -void Species::setGenomeData(genomeData d) { - diploid = d.diploid; - neutralMarkers = d.neutralMarkers; - trait1Chromosome = d.trait1Chromosome; - if (trait1Chromosome) { - if (d.nLoci > 0) nLoci[0] = d.nLoci; - } - if (d.probMutn >= 0.0 && d.probMutn <= 1.0) probMutn = d.probMutn; - if (d.probCrossover >= 0.0 && d.probCrossover <= 1.0) probCrossover = d.probCrossover; - if (d.alleleSD > 0.0) alleleSD = d.alleleSD; - if (d.mutationSD > 0.0) mutationSD = d.mutationSD; -} - -genomeData Species::getGenomeData(void) { - genomeData d; - d.diploid = diploid; - d.neutralMarkers = neutralMarkers; - d.pleiotropic = pleiotropic; - d.trait1Chromosome = trait1Chromosome; - if (nLoci != NULL) d.nLoci = nLoci[0]; - else d.nLoci = 0; - d.probMutn = probMutn; - d.probCrossover = probCrossover; - d.alleleSD = alleleSD; - d.mutationSD = mutationSD; - return d; +bool Species::areMutationsOn(void) { + return mutationsOn; } -bool Species::isDiploid(void) { return diploid; } - -// Chromosome functions - -void Species::setNChromosomes(int c) { - if (nLoci != NULL) deleteLoci(); - if (c > 0) { - nChromosomes = nNLoci = c; - nLoci = new short[c]; - for (int i = 0; i < nNLoci; i++) nLoci[i] = 0; - } - else nChromosomes = nNLoci = 0; -} - -int Species::getNChromosomes(void) { return nChromosomes; } - -void Species::setNLoci(const short chr, const short nloc) { - if (chr >= 0 && chr < nNLoci) { - if (nloc > 0) nLoci[chr] = nloc; - else nLoci[chr] = 0; - } +void Species::resetGeneticParameters() { + mutationsOn = true; + nbGeneticFitnessTraits = 0; + genomeSize = -9999; + recombinationRate = -9999; + nPatchesToSample = 0; + nIndsToSample = ""; + chromosomeEnds.clear(); + samplePatchList.clear(); } -int Species::getNLoci(const short chr) { - if (chr >= 0 && chr < nChromosomes) return nLoci[chr]; - else return 0; +bool Species::isDiploid() const { + return diploid; } -void Species::deleteLoci(void) { - if (nLoci != NULL) { delete[] nLoci; nLoci = NULL; } +int Species::incrNbGenLoadTraits() +{ + nbGeneticFitnessTraits++; + return nbGeneticFitnessTraits; } -// Trait functions - -// Set 1:1 mapping of trait to chromosome -void Species::set1ChromPerTrait(const int nloc) { - nChromosomes = nTraits; - if (nLoci != NULL) deleteLoci(); - nLoci = new short[1]; - if (nloc > 0) nLoci[0] = nloc; - else nLoci[0] = 1; +int Species::getNbGenLoadTraits() const +{ + return nbGeneticFitnessTraits; } -bool Species::has1ChromPerTrait(void) { return trait1Chromosome; } +void Species::addTrait(TraitType traitType, const SpeciesTrait& trait) { -// Set trait attributes for the species -void Species::setTraits(void) { + TraitType traitT = traitType; + // hack to deal with multiple genetic load traits, could be handled better + if (traitType == GENETIC_LOAD) { + int n = incrNbGenLoadTraits(); - emigTrait[0] = 0; emigTrait[1] = 0; - movtTrait[0] = 0; movtTrait[1] = 0; - settTrait[0] = 0; settTrait[1] = 0; - nTraits = 0; -#if RSDEBUG - DebugGUI("Species::setTraits(): 0000 nChromosomes=" + Int2Str(nChromosomes) - + " nTraits=" + Int2Str(nTraits) - + " indVarEmig=" + Int2Str((int)indVarEmig) - + " indVarTrfr=" + Int2Str((int)indVarTrfr) - + " indVarSett=" + Int2Str((int)indVarSett) - ); -#endif - - if (indVarEmig) { - if (sexDepEmig) { - if (densDepEmig) nTraits += 6; else nTraits += 2; + switch (n) { + case 1: + { + traitT = GENETIC_LOAD1; + break; } - else { - if (densDepEmig) nTraits += 3; else nTraits += 1; + case 2: + { + traitT = GENETIC_LOAD2; + break; } - emigTrait[0] = 0; emigTrait[1] = nTraits; - } - - int movttraits = 0; - if (indVarTrfr) { - if (moveModel) { - if (moveType == 1) { // SMS - movttraits = 1; // in contain batch 2 - if (goalType == 2) movttraits += 3; //in contain batch 2 - } - if (moveType == 2) movttraits = 2; - } - else { - if (sexDepTrfr) { - if (twinKern) movttraits = 6; else movttraits = 2; - } - else { - if (twinKern) movttraits = 3; else movttraits = 1; - } - } - movtTrait[0] = nTraits; movtTrait[1] = movttraits; - nTraits += movttraits; - } - - int setttraits = 0; - if (indVarSett) { - if (sexDepSett) setttraits = 6; else setttraits = 3; - settTrait[0] = nTraits; settTrait[1] = setttraits; - nTraits += setttraits; - } - - setTraitNames(); - -#if RSDEBUG - DebugGUI("Species::setTraits(): 9999 nChromosomes=" + Int2Str(nChromosomes) - + " nTraits=" + Int2Str(nTraits)); -#endif - -} - -void Species::setTraitNames(void) { - deleteTraitNames(); - nTraitNames = nTraits; - traitnames = new string[nTraitNames]; - int trait = 0; - if (indVarEmig) { - if (sexDepEmig) { - if (densDepEmig) { - traitnames[trait++] = "d0_F"; - traitnames[trait++] = "d0_M"; - traitnames[trait++] = "alpha_F"; - traitnames[trait++] = "alpha_M"; - traitnames[trait++] = "beta_F"; - traitnames[trait++] = "beta_M"; - } - else { - traitnames[trait++] = "d0_F"; - traitnames[trait++] = "d0_M"; - } - } - else { - traitnames[trait++] = "d0"; - if (densDepEmig) { - traitnames[trait++] = "alpha"; - traitnames[trait++] = "beta"; - } + case 3: + { + traitT = GENETIC_LOAD3; + break; } - } - - if (indVarTrfr) { - if (moveModel) { - if (moveType == 1) { // SMS - traitnames[trait++] = "DP"; - if (goalType == 2) { - traitnames[trait++] = "GB"; - traitnames[trait++] = "alphaDB"; - traitnames[trait++] = "betaDB"; - } - } - if (moveType == 2) { // CRW - traitnames[trait++] = "stepL"; - traitnames[trait++] = "rho"; - } + case 4: + { + traitT = GENETIC_LOAD4; + break; } - else { - if (sexDepTrfr) { - if (twinKern) - { - traitnames[trait++] = "meanDistI_F"; - traitnames[trait++] = "meanDistI_M"; - traitnames[trait++] = "meanDistII_F"; - traitnames[trait++] = "meanDistII_M"; - traitnames[trait++] = "probKernI_F"; - traitnames[trait++] = "probKernI_M"; - } - else { - traitnames[trait++] = "meanDistI_F"; - traitnames[trait++] = "meanDistI_M"; - } - } - else { - traitnames[trait++] = "meanDistI"; - if (twinKern) - { - traitnames[trait++] = "meanDistII"; - traitnames[trait++] = "probKernI"; - } - } + case 5: + { + traitT = GENETIC_LOAD5; + break; } - } - - if (indVarSett) { - if (sexDepSett) { - traitnames[trait++] = "s0_F"; - traitnames[trait++] = "s0_M"; - traitnames[trait++] = "alphaS_F"; - traitnames[trait++] = "alphaS_M"; - traitnames[trait++] = "betaS_F"; - traitnames[trait++] = "betaS_M"; + default: + { + cout << endl << ("Too many genetic load traits in Traits file, max = 5 \n"); + break; } - else { - traitnames[trait++] = "s0"; - traitnames[trait++] = "alphaS"; - traitnames[trait++] = "betaS"; } } + spTraitTable.emplace(traitT, make_unique(trait)); } -void Species::deleteTraitNames(void) { - if (traitnames != NULL) { - delete[] traitnames; - traitnames = NULL; - } +SpeciesTrait* Species::getSpTrait(TraitType trait) const { + return spTraitTable.find(trait)->second.get(); } -string Species::getTraitName(const int trait) { - string name = "not used"; - if (traitnames != NULL) { - if (trait >= 0 && trait < nTraits) { - name = traitnames[trait]; - } - } - return name; +void Species::clearTraitTable() { + spTraitTable.clear(); } -int Species::getNTraits(void) { return nTraits; } - -void Species::setTraitData(const int ntraits) { - deleteTraitData(); - traitdata = new traitData; - if (ntraits > 0) { - traitdata->nTraitMaps = ntraits; - traitdata->traitmaps = new traitMap * [ntraits]; - for (int i = 0; i < ntraits; i++) { - traitdata->traitmaps[i] = new traitMap; - } - } - else { // neutral markers only - traitdata->nTraitMaps = 0; - } - traitdata->neutralloci = new traitMap; - traitdata->neutralloci->nAlleles = 0; -} - -void Species::deleteTraitData(void) { - if (traitdata != NULL) { - for (int i = 0; i < traitdata->nTraitMaps; i++) { - if (traitdata->traitmaps[i]->traitalleles != 0) { - for (int j = 0; j < traitdata->traitmaps[i]->nAlleles; j++) { - delete traitdata->traitmaps[i]->traitalleles[j]; - } - } - delete[] traitdata->traitmaps[i]; - } - deleteNeutralLoci(); - delete traitdata; - traitdata = NULL; - } +set Species::getTraitTypes() { + auto kv = std::ranges::views::keys(spTraitTable); + set keys{ kv.begin(), kv.end() }; + return keys; } -int Species::getNTraitMaps(void) { - if (traitdata == NULL) return 0; - else return traitdata->nTraitMaps; +int Species::getNTraits() const { + return static_cast(spTraitTable.size()); } -void Species::setTraitMap(const short trait, const short nalleles) { - traitdata->traitmaps[trait]->nAlleles = nalleles; - traitdata->traitmaps[trait]->traitalleles = new traitAllele * [nalleles]; - for (int i = 0; i < nalleles; i++) { - traitdata->traitmaps[trait]->traitalleles[i] = new traitAllele; - } +int Species::getNPositionsForTrait(const TraitType trait) const { + return this->getSpTrait(trait)->getPositionsSize(); } -int Species::getNTraitAlleles(const int trait) { - int nalleles = 0; - if (traitdata != NULL) { - if (trait >= 0 && trait < traitdata->nTraitMaps) { - nalleles = traitdata->traitmaps[trait]->nAlleles; - } - } - return nalleles; +int Species::getGenomeSize() const { + return genomeSize; } -void Species::setTraitAllele(const short trait, const short allele, - const short chr, const short loc) -{ - traitdata->traitmaps[trait]->traitalleles[allele] = new traitAllele; - if (chr >= 0 && loc >= 0) { - traitdata->traitmaps[trait]->traitalleles[allele]->chromo = chr; - traitdata->traitmaps[trait]->traitalleles[allele]->locus = loc; - } - else { - traitdata->traitmaps[trait]->traitalleles[allele]->chromo = 0; - traitdata->traitmaps[trait]->traitalleles[allele]->locus = 0; - } +float Species::getRecombinationRate() const { + return recombinationRate; } -traitAllele Species::getTraitAllele(const short trait, const short allele) { - traitAllele a; a.chromo = 0; a.locus = 0; - if (traitdata != NULL) { - if (trait >= 0 && trait < traitdata->nTraitMaps) { - if (allele >= 0 && allele < traitdata->traitmaps[trait]->nAlleles) { - a = *traitdata->traitmaps[trait]->traitalleles[allele]; - } - } - } - return a; -} - -// Neutral loci functions - -// Identify neutral loci and determine whether there is pleiotropy -void Species::setNeutralLoci(bool neutralMarkersOnly) { - bool neutral; - int nneutral = 0; - // find minimum of no. of defined / applied traits - int ntraits; - if (traitdata == 0) ntraits = 0; - else ntraits = traitdata->nTraitMaps; - if (ntraits > nTraits) ntraits = nTraits; - -// determine no. of neutral loci - deleteNeutralLoci(); - for (int i = 0; i < nNLoci; i++) { // each chromosome - for (int j = 0; j < nLoci[i]; j++) { // each locus - neutral = true; - for (int t = 0; t < ntraits; t++) { // each trait - for (int a = 0; a < traitdata->traitmaps[t]->nAlleles; a++) { - if (i == traitdata->traitmaps[t]->traitalleles[a]->chromo - && j == traitdata->traitmaps[t]->traitalleles[a]->locus) { - neutral = false; // as locus contributes to a trait - a = 999999; - } - } - if (!neutral) t = 999999; - } - if (neutral) nneutral++; - } - } - - traitdata->neutralloci = new traitMap; - traitdata->neutralloci->nAlleles = nneutral; - if (nneutral < 1) return; - - // record neutral markers - traitdata->neutralloci->traitalleles = new traitAllele * [nneutral]; - nneutral = 0; - for (int i = 0; i < nNLoci; i++) { // each chromosome - for (int j = 0; j < nLoci[i]; j++) { // each locus - neutral = true; - for (int t = 0; t < ntraits; t++) { // each trait - for (int a = 0; a < traitdata->traitmaps[t]->nAlleles; a++) { - if (i == traitdata->traitmaps[t]->traitalleles[a]->chromo - && j == traitdata->traitmaps[t]->traitalleles[a]->locus) { - neutral = false; // as locus contributes to a trait - a = 999999; - } - } - if (!neutral) t = 999999; - } - if (neutral) { - traitdata->neutralloci->traitalleles[nneutral] = new traitAllele; - traitdata->neutralloci->traitalleles[nneutral]->chromo = i; - traitdata->neutralloci->traitalleles[nneutral]->locus = j; - nneutral++; - } - } - } - - pleiotropic = false; - if (neutralMarkersOnly) return; // pleiotropy cannot apply - - // determine whether there is pleiotropy - int chr, loc; - int nloci = 0; // maximum no. of loci on any one chromosome - for (int i = 0; i < nNLoci; i++) { - if (nloci < nLoci[i]) nloci = nLoci[i]; - } - int*** locfreq; - locfreq = new int** [nNLoci]; - for (int i = 0; i < nNLoci; i++) { - locfreq[i] = new int* [nloci]; - for (int j = 0; j < nloci; j++) { - locfreq[i][j] = new int[ntraits]; - for (int t = 0; t < ntraits; t++) locfreq[i][j][t] = 0; - } - } - for (int t = 0; t < ntraits; t++) { // each trait - for (int a = 0; a < traitdata->traitmaps[t]->nAlleles; a++) { - chr = traitdata->traitmaps[t]->traitalleles[a]->chromo; - loc = traitdata->traitmaps[t]->traitalleles[a]->locus; - locfreq[chr][loc][t]++; - } - } - for (int i = 0; i < nNLoci; i++) { - for (int j = 0; j < nloci; j++) { - // remove multiple contributions of a locus to a particular trait - // (i.e. prevent recording of pseudo-pleiotropy) - for (int t = 0; t < ntraits; t++) { - if (locfreq[i][j][t] > 0) locfreq[i][j][t] = 1; - } - // sum at level of chromosome/locus - for (int t = 1; t < ntraits; t++) { - locfreq[i][j][0] += locfreq[i][j][t]; - } - if (locfreq[i][j][0] > 1) pleiotropic = true; - } - } - for (int i = 0; i < nNLoci; i++) { - for (int j = 0; j < nloci; j++) { - delete[] locfreq[i][j]; - } - delete[] locfreq[i]; - } - delete[] locfreq; - -} - -void Species::deleteNeutralLoci(void) { - if (traitdata->neutralloci != NULL) { - for (int i = 0; i < traitdata->neutralloci->nAlleles; i++) { - delete traitdata->neutralloci->traitalleles[i]; - } - delete[] traitdata->neutralloci; - } - traitdata->neutralloci = NULL; -} - -int Species::getNNeutralLoci(void) { - int nn = 0; - if (traitdata != NULL) { - if (traitdata->neutralloci != NULL) { - nn = traitdata->neutralloci->nAlleles; - } - } - return nn; -} - -traitAllele Species::getNeutralAllele(const short allele) { - traitAllele a; a.chromo = 0; a.locus = 0; - if (traitdata != NULL) { - if (allele >= 0 && allele < traitdata->neutralloci->nAlleles) { - a = *traitdata->neutralloci->traitalleles[allele]; - } - } - return a; +set Species::getChromosomeEnds() const { + return chromosomeEnds; } //--------------------------------------------------------------------------- // Emigration functions - -void Species::setEmig(const emigRules e) { - densDepEmig = e.densDep; stgDepEmig = e.stgDep; sexDepEmig = e.sexDep; +void Species::setEmigRules(const emigRules e) { + densDepEmig = e.densDep; + stgDepEmig = e.stgDep; + sexDepEmig = e.sexDep; indVarEmig = e.indVar; if (e.emigStage >= 0) emigStage = e.emigStage; } -emigRules Species::getEmig(void) { +emigRules Species::getEmigRules(void) { emigRules e; - e.densDep = densDepEmig; e.stgDep = stgDepEmig; e.sexDep = sexDepEmig; - e.indVar = indVarEmig; e.emigStage = emigStage; - e.emigTrait[0] = emigTrait[0]; e.emigTrait[1] = emigTrait[1]; + e.densDep = densDepEmig; + e.stgDep = stgDepEmig; + e.sexDep = sexDepEmig; + e.indVar = indVarEmig; + e.emigStage = emigStage; return e; } -void Species::setEmigTraits(const short stg, const short sex, const emigTraits e) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { +void Species::setSpEmigTraits(const short stg, const short sex, const emigTraits e) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { if (e.d0 >= 0.0 && e.d0 <= 1.0) d0[stg][sex] = e.d0; - alphaEmig[stg][sex] = e.alpha; betaEmig[stg][sex] = e.beta; + alphaEmig[stg][sex] = e.alpha; + betaEmig[stg][sex] = e.beta; } } -emigTraits Species::getEmigTraits(short stg, short sex) { +emigTraits Species::getSpEmigTraits(short stg, short sex) { emigTraits e; - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { - e.d0 = d0[stg][sex]; e.alpha = alphaEmig[stg][sex]; e.beta = betaEmig[stg][sex]; + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { + e.d0 = d0[stg][sex]; + e.alpha = alphaEmig[stg][sex]; + e.beta = betaEmig[stg][sex]; } else { e.d0 = e.alpha = e.beta = 0.0; @@ -918,8 +616,8 @@ emigTraits Species::getEmigTraits(short stg, short sex) { return e; } -float Species::getEmigD0(short stg, short sex) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { +float Species::getSpEmigD0(short stg, short sex) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { return d0[stg][sex]; } else { @@ -927,70 +625,29 @@ float Species::getEmigD0(short stg, short sex) { } } -void Species::setEmigParams(const short stg, const short sex, const emigParams e) { - if (stg >= 0 && stg < 1 && sex >= 0 && sex < NSEXES) // implemented for stage 0 only - { - if (e.d0Mean >= 0.0 && e.d0Mean < 1.0) d0Mean[stg][sex] = e.d0Mean; - if (e.d0SD > 0.0 && e.d0SD < 1.0) d0SD[stg][sex] = e.d0SD; - alphaMean[stg][sex] = e.alphaMean; - if (e.alphaSD > 0.0) alphaSD[stg][sex] = e.alphaSD; - betaMean[stg][sex] = e.betaMean; - if (e.betaSD > 0.0) betaSD[stg][sex] = e.betaSD; - } -} - -emigParams Species::getEmigParams(short stg, short sex) { - emigParams e; - if (stg >= 0 && stg < 1 && sex >= 0 && sex < NSEXES) // implemented for stage 0 only - { - e.d0Mean = d0Mean[stg][sex]; e.d0SD = d0SD[stg][sex]; - e.d0Scale = d0Scale; - e.alphaMean = alphaMean[stg][sex]; e.alphaSD = alphaSD[stg][sex]; - e.alphaScale = alphaScale; - e.betaMean = betaMean[stg][sex]; e.betaSD = betaSD[stg][sex]; - e.betaScale = betaScale; - } - else { - e.d0Mean = e.alphaMean = e.betaMean = e.d0SD = e.alphaSD = e.betaSD = 0.0; - e.d0Scale = d0Scale; - e.alphaScale = alphaScale; - e.betaScale = betaScale; - } - return e; -} - -void Species::setEmigScales(const emigScales s) { - if (s.d0Scale >= 0.0 && s.d0Scale < 1.0) d0Scale = s.d0Scale; - if (s.alphaScale >= 0.0) alphaScale = s.alphaScale; - if (s.betaScale >= 0.0) betaScale = s.betaScale; -} - -emigScales Species::getEmigScales(void) { - emigScales s; - s.d0Scale = d0Scale; s.alphaScale = alphaScale; s.betaScale = betaScale; - return s; -} - //--------------------------------------------------------------------------- // Transfer functions -void Species::setTrfr(const trfrRules t) { - moveModel = t.moveModel; stgDepTrfr = t.stgDep; sexDepTrfr = t.sexDep; - distMort = t.distMort; indVarTrfr = t.indVar; +void Species::setTrfrRules(const transferRules t) { + usesMovtProcess = t.usesMovtProc; + stgDepTrfr = t.stgDep; + sexDepTrfr = t.sexDep; + distMort = t.distMort; + indVarTrfr = t.indVar; twinKern = t.twinKern; habMort = t.habMort; - moveType = t.moveType; costMap = t.costMap; + moveType = t.moveType; + costMap = t.costMap; } -trfrRules Species::getTrfr(void) { - trfrRules t; - t.moveModel = moveModel; t.stgDep = stgDepTrfr; t.sexDep = sexDepTrfr; +transferRules Species::getTransferRules(void) { + transferRules t; + t.usesMovtProc = usesMovtProcess; t.stgDep = stgDepTrfr; t.sexDep = sexDepTrfr; t.distMort = distMort; t.indVar = indVarTrfr; t.twinKern = twinKern; t.habMort = habMort; t.moveType = moveType; t.costMap = costMap; - t.movtTrait[0] = movtTrait[0]; t.movtTrait[1] = movtTrait[1]; return t; } @@ -1000,19 +657,19 @@ void Species::setFullKernel(bool k) { bool Species::useFullKernel(void) { return fullKernel; } -void Species::setKernTraits(const short stg, const short sex, - const trfrKernTraits k, const int resol) +void Species::setSpKernTraits(const short stg, const short sex, + const trfrKernelParams k, const int resol) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { if (k.meanDist1 > 0.0 && k.meanDist1 >= (float)resol) meanDist1[stg][sex] = k.meanDist1; if (k.meanDist2 >= (float)resol) meanDist2[stg][sex] = k.meanDist2; - if (k.probKern1 > 0.0 && k.probKern1 < 1.0) probKern1[stg][sex] = k.probKern1; + if (k.probKern1 >= 0.0 && k.probKern1 <= 1.0) probKern1[stg][sex] = k.probKern1; } } -trfrKernTraits Species::getKernTraits(short stg, short sex) { - trfrKernTraits k; - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { +trfrKernelParams Species::getSpKernTraits(short stg, short sex) { + trfrKernelParams k; + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { k.meanDist1 = meanDist1[stg][sex]; k.meanDist2 = meanDist2[stg][sex]; k.probKern1 = probKern1[stg][sex]; @@ -1024,7 +681,7 @@ trfrKernTraits Species::getKernTraits(short stg, short sex) { } void Species::setMortParams(const trfrMortParams m) { - if (m.fixedMort >= 0.0 && m.fixedMort < 1.0) fixedMort = m.fixedMort; + if (m.fixedMort >= 0.0 && m.fixedMort <= 1.0) fixedMort = m.fixedMort; mortAlpha = m.mortAlpha; mortBeta = m.mortBeta; } @@ -1035,7 +692,7 @@ trfrMortParams Species::getMortParams(void) { return m; } -void Species::setMovtTraits(const trfrMovtTraits m) { +void Species::setSpMovtTraits(const trfrMovtParams m) { if (m.pr >= 1) pr = m.pr; if (m.prMethod >= 1 && m.prMethod <= 3) prMethod = m.prMethod; if (m.memSize >= 1 && m.memSize <= 14) memSize = m.memSize; @@ -1044,155 +701,35 @@ void Species::setMovtTraits(const trfrMovtTraits m) { if (m.gb >= 1.0) gb = m.gb; if (m.alphaDB > 0.0) alphaDB = m.alphaDB; if (m.betaDB > 0) betaDB = m.betaDB; - if (m.stepMort >= 0.0 && m.stepMort < 1.0) stepMort = m.stepMort; + if (m.stepMort >= 0.0 && m.stepMort <= 1.0) stepMort = m.stepMort; if (m.stepLength > 0.0) stepLength = m.stepLength; - if (m.rho > 0.0 && m.rho < 1.0) rho = m.rho; - straigtenPath = m.straigtenPath; + if (m.rho > 0.0 && m.rho <= 1.0) rho = m.rho; + straightenPath = m.straightenPath; } -trfrMovtTraits Species::getMovtTraits(void) { - trfrMovtTraits m; +trfrMovtParams Species::getSpMovtTraits(void) { + trfrMovtParams m; m.pr = pr; m.prMethod = prMethod; m.memSize = memSize; m.goalType = goalType; m.dp = dp; m.gb = gb; m.alphaDB = alphaDB; m.betaDB = betaDB; m.stepMort = stepMort; m.stepLength = stepLength; m.rho = rho; return m; } -trfrCRWTraits Species::getCRWTraits(void) { +trfrCRWTraits Species::getSpCRWTraits(void) { trfrCRWTraits m; m.stepMort = stepMort; m.stepLength = stepLength; m.rho = rho; - m.straigtenPath = straigtenPath; + m.straightenPath = straightenPath; return m; } -trfrSMSTraits Species::getSMSTraits(void) { +trfrSMSTraits Species::getSpSMSTraits(void) { trfrSMSTraits m; m.pr = pr; m.prMethod = prMethod; m.memSize = memSize; m.goalType = goalType; m.dp = dp; m.gb = gb; m.alphaDB = alphaDB; m.betaDB = betaDB; m.stepMort = stepMort; - m.straigtenPath = straigtenPath; - return m; -} - -void Species::setKernParams(const short stg, const short sex, - const trfrKernParams k, const double resol) -{ - if (stg >= 0 && stg < 1 && sex >= 0 && sex < NSEXES) // implemented for stage 0 only - { - if (k.dist1Mean > 0.0 && k.dist1Mean >= resol && k.dist1SD > 0.0) { - dist1Mean[stg][sex] = k.dist1Mean; dist1SD[stg][sex] = k.dist1SD; - } - if (k.dist2Mean > 0.0 && k.dist2Mean >= resol && k.dist2SD > 0.0) { - dist2Mean[stg][sex] = k.dist2Mean; dist2SD[stg][sex] = k.dist2SD; - } - if (k.PKern1Mean > 0.0 && k.PKern1Mean < 1.0 && k.PKern1SD > 0.0 && k.PKern1SD < 1.0) { - PKern1Mean[stg][sex] = k.PKern1Mean; PKern1SD[stg][sex] = k.PKern1SD; - } - } -} - -trfrKernParams Species::getKernParams(short stg, short sex) { - trfrKernParams k; - if (stg >= 0 && stg < 1 && sex >= 0 && sex < NSEXES) // implemented for stage 0 only - { - k.dist1Mean = dist1Mean[stg][sex]; k.dist1SD = dist1SD[stg][sex]; - k.dist2Mean = dist2Mean[stg][sex]; k.dist2SD = dist2SD[stg][sex]; - k.PKern1Mean = PKern1Mean[stg][sex]; k.PKern1SD = PKern1SD[stg][sex]; - k.dist1Scale = dist1Scale; k.dist2Scale = dist2Scale; k.PKern1Scale = PKern1Scale; - } - else { - k.dist1Mean = 100000.0; k.dist1SD = 0.001f; k.dist1Scale = 1.0; - k.dist2Mean = 100000.0; k.dist2SD = 0.001f; k.dist2Scale = 1.0; - k.PKern1Mean = 0.5; k.PKern1SD = 0.1f; k.PKern1Scale = 0.1f; - } - return k; -} - -void Species::setSMSParams(const short stg, const short sex, const trfrSMSParams s) { - if (stg >= 0 && stg < 1 && sex >= 0 && sex < 1) // implemented for stage 0 & sex 0 only - { - if (s.dpMean >= 1.0 && s.dpSD > 0.0) { - dpMean[stg][sex] = s.dpMean; dpSD[stg][sex] = s.dpSD; - } - if (s.gbMean >= 1.0 && s.gbSD > 0.0) { - gbMean[stg][sex] = s.gbMean; gbSD[stg][sex] = s.gbSD; - } - if (s.alphaDBMean > 0.0 && s.alphaDBSD > 0.0) { - alphaDBMean[stg][sex] = s.alphaDBMean; alphaDBSD[stg][sex] = s.alphaDBSD; - } - if (s.betaDBMean >= 1.0 && s.betaDBSD > 0.0) { - betaDBMean[stg][sex] = s.betaDBMean; betaDBSD[stg][sex] = s.betaDBSD; - } - } -} - -trfrSMSParams Species::getSMSParams(short stg, short sex) { - trfrSMSParams s; - if (stg >= 0 && stg < 1 && sex >= 0 && sex < 1) // implemented for stage 0 & sex 0 only - { - s.dpMean = dpMean[stg][sex]; s.dpSD = dpSD[stg][sex]; - s.gbMean = gbMean[stg][sex]; s.gbSD = gbSD[stg][sex]; - s.alphaDBMean = alphaDBMean[stg][sex]; s.alphaDBSD = alphaDBSD[stg][sex]; - s.betaDBMean = betaDBMean[stg][sex]; s.betaDBSD = betaDBSD[stg][sex]; - s.dpScale = dpScale; s.gbScale = gbScale; - s.alphaDBScale = alphaDBScale; s.betaDBScale = betaDBScale; - } - else { - s.dpMean = 1.0; s.dpSD = 0.1f; s.dpScale = 0.1f; - s.gbMean = 1.0; s.gbSD = 0.1f; s.gbScale = 0.1f; - s.alphaDBMean = 1.0; s.alphaDBSD = 0.1f; s.alphaDBScale = 0.1f; - s.betaDBMean = 10.0; s.betaDBSD = 1.0; s.betaDBScale = 1.0; - } - return s; -} - -void Species::setCRWParams(const short stg, const short sex, const trfrCRWParams m) { - if (stg >= 0 && stg < 1 && sex >= 0 && sex < 1) // implemented for stage 0 & sex 0 only - { - if (m.stepLgthMean > 0.0 && m.stepLgthSD > 0.0) { - stepLgthMean[stg][sex] = m.stepLgthMean; stepLgthSD[stg][sex] = m.stepLgthSD; - } - if (m.rhoMean > 0.0 && m.rhoMean < 1.0 && m.rhoSD > 0.0 && m.rhoSD < 1.0) { - rhoMean[stg][sex] = m.rhoMean; rhoSD[stg][sex] = m.rhoSD; - } - } -} - -trfrCRWParams Species::getCRWParams(short stg, short sex) { - trfrCRWParams m; - if (stg >= 0 && stg < 1 && sex >= 0 && sex < 1) // implemented for stage 0 & sex 0 only - { - m.stepLgthMean = stepLgthMean[stg][sex]; m.stepLgthSD = stepLgthSD[stg][sex]; - m.rhoMean = rhoMean[stg][sex]; m.rhoSD = rhoSD[stg][sex]; - m.stepLScale = stepLScale; m.rhoScale = rhoScale; - } - else { - m.stepLgthMean = 1.0; m.stepLgthSD = 0.1f; m.stepLScale = 0.1f; - m.rhoMean = 0.5; m.rhoSD = 0.1f; m.rhoScale = 0.1f; - } + m.straightenPath = straightenPath; return m; } -void Species::setTrfrScales(const trfrScales s) { - if (s.dist1Scale >= 0.0) dist1Scale = s.dist1Scale; - if (s.dist2Scale >= 0.0) dist2Scale = s.dist2Scale; - if (s.PKern1Scale > 0.0 && s.PKern1Scale < 1.0) PKern1Scale = s.PKern1Scale; - if (s.dpScale > 0.0) dpScale = s.dpScale; - if (s.gbScale > 0.0) gbScale = s.gbScale; - if (s.alphaDBScale > 0.0) alphaDBScale = s.alphaDBScale; - if (s.betaDBScale > 0.0) betaDBScale = s.betaDBScale; - if (s.stepLScale > 0.0) stepLScale = s.stepLScale; - if (s.rhoScale > 0.0 && s.rhoScale < 1.0) rhoScale = s.rhoScale; -} - -trfrScales Species::getTrfrScales(void) { - trfrScales s; - s.dist1Scale = dist1Scale; s.dist2Scale = dist2Scale; s.PKern1Scale = PKern1Scale; - s.dpScale = dpScale; s.gbScale = gbScale; - s.alphaDBScale = alphaDBScale; s.betaDBScale = betaDBScale; - s.stepLScale = stepLScale; s.rhoScale = rhoScale; - return s; -} - short Species::getMovtHabDim() { return habDimTrfr; } void Species::createHabCostMort(short nhab) { @@ -1251,12 +788,11 @@ void Species::setSettle(const settleType s) { settleType Species::getSettle(void) { settleType s; s.stgDep = stgDepSett; s.sexDep = sexDepSett; s.indVar = indVarSett; - s.settTrait[0] = settTrait[0]; s.settTrait[1] = settTrait[1]; return s; } void Species::setSettRules(const short stg, const short sex, const settleRules s) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { densDepSett[stg][sex] = s.densDep; wait[stg][sex] = s.wait; go2nbrLocn[stg][sex] = s.go2nbrLocn; findMate[stg][sex] = s.findMate; } @@ -1268,7 +804,7 @@ settleRules Species::getSettRules(short stg, short sex) { s.findMate = false; s.go2nbrLocn = false; s.wait = false; - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { s.densDep = densDepSett[stg][sex]; s.wait = wait[stg][sex]; s.go2nbrLocn = go2nbrLocn[stg][sex]; s.findMate = findMate[stg][sex]; } @@ -1276,7 +812,7 @@ settleRules Species::getSettRules(short stg, short sex) { } void Species::setSteps(const short stg, const short sex, const settleSteps s) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { if (s.maxStepsYr >= 1) maxStepsYr[stg][sex] = s.maxStepsYr; else maxStepsYr[stg][sex] = 99999999; if (s.minSteps >= 0) minSteps[stg][sex] = s.minSteps; @@ -1288,7 +824,7 @@ void Species::setSteps(const short stg, const short sex, const settleSteps s) { settleSteps Species::getSteps(short stg, short sex) { settleSteps s; - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { s.maxStepsYr = maxStepsYr[stg][sex]; s.minSteps = minSteps[stg][sex]; s.maxSteps = maxSteps[stg][sex]; @@ -1301,69 +837,89 @@ settleSteps Species::getSteps(short stg, short sex) { return s; } -void Species::setSettTraits(const short stg, const short sex, const settleTraits dd) { - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { +void Species::setSpSettTraits(const short stg, const short sex, const settleTraits dd) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { if (dd.s0 > 0.0 && dd.s0 <= 1.0) s0[stg][sex] = dd.s0; alphaS[stg][sex] = dd.alpha; betaS[stg][sex] = dd.beta; } } -settleTraits Species::getSettTraits(short stg, short sex) { +settleTraits Species::getSpSettTraits(short stg, short sex) { settleTraits dd; - if (stg >= 0 && stg < NSTAGES && sex >= 0 && sex < NSEXES) { + if (stg >= 0 && stg < gMaxNbStages && sex >= 0 && sex < gMaxNbSexes) { dd.s0 = s0[stg][sex]; dd.alpha = alphaS[stg][sex]; dd.beta = betaS[stg][sex]; } else { dd.s0 = 1.0; dd.alpha = dd.beta = 0.0; } return dd; } -void Species::setSettParams(const short stg, const short sex, const settParams s) { - if (stg >= 0 && stg < 1 && sex >= 0 && sex < NSEXES) // implemented for stage 0 only - { - if (s.s0Mean >= 0.0 && s.s0Mean < 1.0) s0Mean[stg][sex] = s.s0Mean; - if (s.s0SD > 0.0 && s.s0SD < 1.0) s0SD[stg][sex] = s.s0SD; - alphaSMean[stg][sex] = s.alphaSMean; - if (s.alphaSSD > 0.0) alphaSSD[stg][sex] = s.alphaSSD; - betaSMean[stg][sex] = s.betaSMean; - if (s.betaSSD > 0.0) betaSSD[stg][sex] = s.betaSSD; - if (sex == 0) { - if (s.s0Scale > 0.0 && s.s0Scale < 1.0) s0Scale = s.s0Scale; - if (s.alphaSScale > 0.0) alphaSScale = s.alphaSScale; - if (s.betaSScale > 0.0) betaSScale = s.betaSScale; - } - } +void Species::setGeneticParameters(const std::set& chromosomeEnds, const int genomeSize, const float recombinationRate, + const std::set& samplePatchList, const string nIndsToSample, const std::set& stagesToSampleFrom, int nPatchesToSampleFrom) +{ + this->genomeSize = genomeSize; + this->chromosomeEnds = chromosomeEnds; + this->recombinationRate = recombinationRate; + this->samplePatchList = samplePatchList; + this->nPatchesToSample = nPatchesToSampleFrom; + this->nIndsToSample = nIndsToSample; + this->stagesToSampleFrom = stagesToSampleFrom; } -settParams Species::getSettParams(short stg, short sex) { - settParams s; - if (stg >= 0 && stg < 1 && sex >= 0 && sex < NSEXES) // implemented for stage 0 only - { - s.s0Mean = s0Mean[stg][sex]; s.s0SD = s0SD[stg][sex]; - s.alphaSMean = alphaSMean[stg][sex]; s.alphaSSD = alphaSSD[stg][sex]; - s.betaSMean = betaSMean[stg][sex]; s.betaSSD = betaSSD[stg][sex]; - } - else { - s.s0Mean = s.alphaSMean = s.betaSMean = s.s0SD = s.alphaSSD = s.betaSSD = 0.0; - } - s.s0Scale = s0Scale; - s.alphaSScale = alphaSScale; - s.betaSScale = betaSScale; - return s; +// only called for cell based landscape +void Species::setSamplePatchList(const set& samplePatchList) { + this->samplePatchList = samplePatchList; } -void Species::setSettScales(const settScales s) { - if (s.s0Scale >= 0.0 && s.s0Scale < 1.0) s0Scale = s.s0Scale; - if (s.alphaSScale >= 0.0) alphaSScale = s.alphaSScale; - if (s.betaSScale >= 0.0) betaSScale = s.betaSScale; +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +#ifdef UNIT_TESTS +// For testing purposes only + +Species* createDefaultSpecies() { + short repType = 0; + short repSeasons = 1; + bool stagestruct = false; + int nStages = 2; + bool usesMovtProc = false; + short movtType = 1; + Species* pSpecies = new Species(repType, repSeasons, stagestruct, nStages, usesMovtProc, movtType); + return pSpecies; } -settScales Species::getSettScales(void) { - settScales s; - s.s0Scale = s0Scale; s.alphaSScale = alphaSScale; s.betaSScale = betaSScale; - return s; +// Set kernel parameters, but ignore constraints on values +// Used to test dispersal with values < resolution +void Species::overrideKernels(const short stg, const short sex, + const trfrKernelParams k) +{ + meanDist1[stg][sex] = k.meanDist1; + meanDist2[stg][sex] = k.meanDist2; + probKern1[stg][sex] = k.probKern1; } -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- -//--------------------------------------------------------------------------- +demogrParams createDefaultHaploidDemogrParams() { + demogrParams d; + d.repType = 0; + d.repSeasons = 1; + d.stageStruct = false; + d.propMales = 0.0; + d.harem = 1.0; + d.bc = 1.0; + d.lambda = 2.0; + return d; +} + +demogrParams createDefaultDiploidDemogrParams() { + demogrParams d; + d.repType = 1; + d.repSeasons = 1; + d.stageStruct = false; + d.propMales = 0.5; + d.harem = 1.0; + d.bc = 1.0; + d.lambda = 2.0; + return d; +} +#endif // UNIT_TESTS diff --git a/Species.h b/Species.h index 8b6c07f..eebd544 100644 --- a/Species.h +++ b/Species.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -45,140 +45,166 @@ #ifndef SpeciesH #define SpeciesH +#include +#include +#include +#include + #include "Parameters.h" +#include "SpeciesTrait.h" +#include "QuantitativeTrait.h" + +class SpeciesTrait; -// structures for demographic parameters + // structures for demographic parameters struct demogrParams { short repType; short repSeasons; - float propMales; float harem; float bc; float lambda; + float propMales; + float harem; + float bc; + float lambda; bool stageStruct; }; struct stageParams { - short nStages; short repInterval; short maxAge; short survival; + short nStages; + short repInterval; + short maxAge; + short survival; float probRep; - bool fecDens; bool fecStageDens; bool devDens; bool devStageDens; - bool survDens; bool survStageDens; bool disperseOnLoss; + bool fecDens; + bool fecStageDens; + bool devDens; + bool devStageDens; + bool survDens; + bool survStageDens; + bool disperseOnLoss; }; struct densDepParams { - float devCoeff; float survCoeff; -}; - -// structures for genetics - -struct genomeData { - int nLoci; - bool diploid; bool neutralMarkers; bool pleiotropic; bool trait1Chromosome; - double probMutn, probCrossover, alleleSD, mutationSD; -}; - -struct traitAllele { - short chromo; short locus; -}; - -struct traitMap { - short nAlleles; - traitAllele** traitalleles; -}; - -struct traitData { - short nTraitMaps; - traitMap** traitmaps; - traitMap* neutralloci; + float devCoeff; + float survCoeff; }; // structures for emigration parameters struct emigRules { - bool densDep; bool stgDep; bool sexDep; bool indVar; + bool densDep; + bool stgDep; + bool sexDep; + bool indVar; short emigStage; short emigTrait[2]; }; struct emigTraits { - float d0; float alpha; float beta; -}; -struct emigParams { - double d0Mean; double d0SD; double d0Scale; - double alphaMean; double alphaSD; double alphaScale; - double betaMean; double betaSD; double betaScale; -}; -struct emigScales { - double d0Scale; double alphaScale; double betaScale; + float d0; + float alpha; + float beta; + + emigTraits() : d0(0.0), alpha(0.0), beta(0.0) {}; + + emigTraits(const emigTraits& e) : d0(e.d0), alpha(e.alpha), beta(e.beta) {}; + + emigTraits* clone() { return new emigTraits(*this); } + + void divideTraitsBy(int i) { + + d0 /= i; + alpha /= i; + beta /= i; + } }; // structures for transfer parameters -struct trfrRules { - bool moveModel; bool stgDep; bool sexDep; - bool distMort; bool indVar; +struct transferRules { + bool usesMovtProc; + bool stgDep; + bool sexDep; + bool distMort; + bool indVar; bool twinKern; bool habMort; - short moveType; bool costMap; + short moveType; + bool costMap; short movtTrait[2]; }; -struct trfrKernTraits { - float meanDist1; float meanDist2; float probKern1; +struct trfrKernelParams { + float meanDist1; + float meanDist2; + float probKern1; }; struct trfrMortParams { - float fixedMort; float mortAlpha; float mortBeta; + float fixedMort; + float mortAlpha; + float mortBeta; }; -struct trfrMovtTraits { - short pr; short prMethod; short memSize; short goalType; - float dp; float gb; float alphaDB; int betaDB; - float stepMort; float stepLength; float rho; - bool straigtenPath; +struct trfrMovtParams { + short pr; + short prMethod; + short memSize; + short goalType; + float dp; + float gb; + float alphaDB; + int betaDB; + float stepMort; + float stepLength; + float rho; + bool straightenPath; }; struct trfrCRWTraits { - float stepMort; float stepLength; float rho; bool straigtenPath; + float stepMort; + float stepLength; + float rho; + bool straightenPath; }; struct trfrSMSTraits { - short pr; short prMethod; short memSize; short goalType; - float dp; float gb; float alphaDB; int betaDB; float stepMort; - bool straigtenPath; -}; -struct trfrKernParams { - double dist1Mean; double dist1SD; double dist1Scale; - double dist2Mean; double dist2SD; double dist2Scale; - double PKern1Mean; double PKern1SD; double PKern1Scale; -}; -struct trfrSMSParams { - double dpMean; double dpSD; double gbMean; double gbSD; - double alphaDBMean; double alphaDBSD; double betaDBMean; double betaDBSD; - double dpScale; double gbScale; double alphaDBScale; double betaDBScale; -}; -struct trfrCRWParams { - double stepLgthMean; double stepLgthSD; double stepLScale; - double rhoMean; double rhoSD; double rhoScale; -}; -struct trfrScales { - float dist1Scale; float dist2Scale; float PKern1Scale; - float dpScale; float gbScale; float alphaDBScale; float betaDBScale; - float stepLScale; float rhoScale; + short pr; + short prMethod; + short memSize; + short goalType; + float dp; + float gb; + float alphaDB; + int betaDB; + float stepMort; + bool straightenPath; }; // structures for settlement parameters struct settleType { - bool stgDep; bool sexDep; bool indVar; + bool stgDep; + bool sexDep; + bool indVar; short settTrait[2]; }; struct settleRules { - bool densDep; bool wait; bool go2nbrLocn; bool findMate; + bool densDep; + bool wait; + bool go2nbrLocn; + bool findMate; }; struct settleSteps { - int minSteps; int maxSteps; int maxStepsYr; + int minSteps; + int maxSteps; + int maxStepsYr; }; struct settleTraits { - float s0; float alpha; float beta; -}; -struct settParams { - double s0Mean; double s0SD; double s0Scale; - double alphaSMean; double alphaSSD; double alphaSScale; - double betaSMean; double betaSSD; double betaSScale; -}; -struct settScales { - double s0Scale; double alphaSScale; double betaSScale; + float s0; + float alpha; + float beta; + + settleTraits() : s0(0.0), alpha(0.0), beta(0.0) {}; + + settleTraits(const settleTraits& e) : s0(e.s0), alpha(e.alpha), beta(e.beta) {}; + + void divideTraitsBy(int i) { + s0 /= i; + alpha /= i; + beta /= i; + } }; @@ -187,7 +213,14 @@ struct settScales { class Species { public: - Species(void); + Species( + const short& repro = 0, + const short& nbRepSeasons = 1, + const bool& hasStgStruct = false, + const short& nStg = 2, + const bool& usesMovtProc = false, + const short& movementType = 1 + ); ~Species(void); short getSpNum(void); @@ -208,11 +241,11 @@ class Species { void setStage( // Set stage structure parameters const stageParams // structure holding stage structure parameters ); - stageParams getStage(void); // Get stage structure parameters + stageParams getStageParams(void); // Get stage structure parameters void setDemogr( // Set general demographic parameters const demogrParams // structure holding general demographic parameters ); - demogrParams getDemogr(void); // Get general demographic parameters + demogrParams getDemogrParams(void); // Get general demographic parameters short getRepType(void); bool stageStructured(void); void setDensDep( // Set demographic density dependence coefficients @@ -249,6 +282,45 @@ class Species { short // sex ); + void setFecSpatial(bool); + bool getFecSpatial(void){return fecSpatial;}; + void setDevSpatial(bool); + bool getDevSpatial(void){return devSpatial;}; + void setSurvSpatial(bool); + bool getSurvSpatial(void){return survSpatial;}; + void setFecLayer( // set the layer of the spatial demographic scaling used for fecundity + short, // stage + short, // sex + short // layer + ); + + short getFecLayer( // get the layer of the spatial demographic scaling used for fecundity + short, // stage + short // sex + ); + + void setDevLayer( // set the layer of the spatial demographic scaling used for development + short, // stage + short, // sex + short // layer + ); + + short getDevLayer( // get the layer of the spatial demographic scaling used for development + short, // stage + short // sex + ); + + void setSurvLayer( // set the layer of the spatial demographic scaling used for survival + short, // stage + short, // sex + short // layer + ); + + short getSurvLayer( // get the layer of the spatial demographic scaling used for survival + short, // stage + short // sex + ); + float getMaxFec(void); // Get highest fecundity of any stage void setMinAge( // Set minimum age short, // stage @@ -307,114 +379,73 @@ class Species { short // option: 0 = return minimum, otherwise = return maximum ); + std::set& getSamplePatches() { + return samplePatchList; + }; - // genome functions + string getNIndsToSample() { + return nIndsToSample; + }; - void setGenomeData(genomeData); - genomeData getGenomeData(void); - bool isDiploid(void); - void setNChromosomes( // Set no. of chromosomes - int // no. of chromosomes - ); - int getNChromosomes(void); - void setNLoci( - const short, // chromosome no. - const short // locus no. - ); - int getNLoci( - const short // chromosome no. - ); - void deleteLoci(void); - void set1ChromPerTrait( // Set 1:1 mapping of trait to chromosome - const int // no. of loci on each chromosome - ); - bool has1ChromPerTrait(void); - void setTraits(void); // Set trait attributes for the species - void setTraitNames(void); - void deleteTraitNames(void); - string getTraitName( - const int // trait no. - ); - int getNTraits(void); - void setTraitData( - const int // no. of traits - ); - void deleteTraitData(void); - int getNTraitMaps(void); - void setTraitMap( - const short, // trait no. - const short // no. of alleles - ); - int getNTraitAlleles( - const int // trait no. - ); - void setTraitAllele( - const short, // trait no. - const short, // trait allele no. - const short, // chromosome no. - const short // chromosome locus no. - ); - traitAllele getTraitAllele( - const short, // trait no. - const short // trait allele no. - ); - void setNeutralLoci(bool); - void deleteNeutralLoci(void); - int getNNeutralLoci(void); - traitAllele getNeutralAllele( - const short // allele no. - ); + std::set& getStagesToSample() { + return stagesToSampleFrom; + } + + int getNbPatchesToSample() { + return nPatchesToSample; + } + + // Genetic functions + void resetGeneticParameters(); + bool areMutationsOn(void); + bool isDiploid() const; + int incrNbGenLoadTraits(); + int getNbGenLoadTraits() const; // emigration parameter functions - void setEmig( // Set emigration rules + void setEmigRules( // Set emigration rules const emigRules // structure holding emigration rules ); - emigRules getEmig(void); // Get emigration rules - void setEmigTraits( // Set emigration trait parameters + emigRules getEmigRules(void); // Get emigration rules + void setSpEmigTraits( // Set emigration trait parameters const short, // stage const short, // sex const emigTraits // structure holding emigration trait parameters ); - emigTraits getEmigTraits( // Get emigration trait parameters + emigTraits getSpEmigTraits( // Get emigration trait parameters short, // stage short // sex ); - float getEmigD0( // Get (maximum) emigration probability + float getSpEmigD0( // Get (maximum) emigration probability short, // stage short // sex ); - void setEmigParams( // Set emigration initialisation parameters - const short, // stage (NB implemented for stage 0 only) - const short, // sex - const emigParams // structure holding parameters - ); - emigParams getEmigParams( // Get emigration initialisation parameters - short, // stage (NB implemented for stage 0 only) - short // sex - ); - void setEmigScales( // Set emigration mutation parameters - const emigScales // structure holding emigration mutation parameters - ); - emigScales getEmigScales(void); // Get emigration mutation parameters // transfer parameter functions - void setTrfr( // Set transfer rules - const trfrRules // structure holding transfer rules + void setTrfrRules( // Set transfer rules + const transferRules // structure holding transfer rules ); - trfrRules getTrfr(void); // Get transfer rules + transferRules getTransferRules(void); // Get transfer rules void setFullKernel( // Set fullKernel condition bool // fullKernel value ); bool useFullKernel(void); - void setKernTraits( // Set transfer by kernel parameters + void setSpKernTraits( // Set transfer by kernel parameters const short, // stage const short, // sex - const trfrKernTraits, // structure holding transfer by kernel parameters + const trfrKernelParams, // structure holding transfer by kernel parameters const int // Landscape resolution ); - trfrKernTraits getKernTraits( // Get transfer by kernel parameters + +#ifdef UNIT_TESTS + // Testing: set dispersal but ignore resolution + void overrideKernels(const short stg, const short sex, + const trfrKernelParams k); +# endif // UNIT_TESTS + + trfrKernelParams getSpKernTraits( // Get transfer by kernel parameters short, // stage short // sex ); @@ -422,44 +453,12 @@ class Species { const trfrMortParams // structure holding transfer mortality parameters ); trfrMortParams getMortParams(void); // Get transfer mortality parameters - void setMovtTraits( // Set transfer movement model parameters - const trfrMovtTraits // structure holding transfer movement model parameters - ); - trfrMovtTraits getMovtTraits(void); // Get transfer movement model traits - trfrCRWTraits getCRWTraits(void); // Get CRW traits - trfrSMSTraits getSMSTraits(void); // Get SMS traits - void setKernParams( // Set initial transfer by kernel parameter limits - const short, // stage (NB implemented for stage 0 only) - const short, // sex - const trfrKernParams, // structure holding min and max values - const double // Landscape resolution - ); - trfrKernParams getKernParams( // Get initial transfer by kernel parameter limits - short, // stage (NB implemented for stage 0 only) - short // sex - ); - void setSMSParams( // Set initial transfer by SMS parameter limits - const short, // stage (NB implemented for stage 0 only) - const short, // sex (NB implemented for sex 0 only) - const trfrSMSParams // structure holding min and max values - ); - trfrSMSParams getSMSParams( // Get initial transfer by SMS parameter limits - short, // stage (NB implemented for stage 0 only) - short // sex (NB implemented for sex 0 only) + void setSpMovtTraits( // Set transfer movement model parameters + const trfrMovtParams // structure holding transfer movement model parameters ); - void setCRWParams( // Set initial transfer by CRW parameter limits - const short, // stage (NB implemented for stage 0 only) - const short, // sex (NB implemented for sex 0 only) - const trfrCRWParams // structure holding min and max values - ); - trfrCRWParams getCRWParams( // Get initial transfer by CRW parameter limits - short, // stage (NB implemented for stage 0 only) - short // sex (NB implemented for sex 0 only) - ); - void setTrfrScales( // Set transfer mutation parameters - const trfrScales // structure holding transfer mutation parameters - ); - trfrScales getTrfrScales(void); // Get transfer mutation parameters + trfrMovtParams getSpMovtTraits(void); // Get transfer movement model traits + trfrCRWTraits getSpCRWTraits(void); // Get CRW traits + trfrSMSTraits getSpSMSTraits(void); // Get SMS traits // Return dimension of habitat-dependent step mortality and costs matrices short getMovtHabDim(void); void createHabCostMort( // Create habitat-dependent costs and mortality matrices @@ -505,28 +504,32 @@ class Species { short, // stage short // sex ); - void setSettTraits( // Set settlement density dependence traits + void setSpSettTraits( // Set settlement density dependence traits const short, // stage const short, // sex const settleTraits // structure holding density dependence traits ); - settleTraits getSettTraits( // Get settlement density dependence traits + settleTraits getSpSettTraits( // Get settlement density dependence traits short, // stage short // sex ); - void setSettParams( // Set settlement initialisation parameters - const short, // stage (NB implemented for stage 0 only) - const short, // sex - const settParams // structure holding parameters - ); - settParams getSettParams( // Get settlement initialisation parameters - short, // stage (NB implemented for stage 0 only) - short // sex - ); - void setSettScales( // Set settlement mutation parameters - const settScales // structure holding settlement mutation parameters - ); - settScales getSettScales(void); // Get settlement mutation parameters + + void addTrait(TraitType traitType, const SpeciesTrait& trait); + + void clearTraitTable(); + + SpeciesTrait* getSpTrait(TraitType trait) const; + + std::set getTraitTypes(); + + int getNTraits() const; + int getNPositionsForTrait(const TraitType trait) const; + int getGenomeSize() const; + float getRecombinationRate() const; + std::set getChromosomeEnds() const; + void setGeneticParameters(const std::set& chromosomeEnds, const int genomeSize, const float recombinationRate, + const std::set& samplePatchList, const string nIndsToSample, const std::set& stagesToSampleFrom, int nPatchesToSampleFrom); + void setSamplePatchList(const std::set& samplePatchList); private: @@ -555,6 +558,7 @@ class Species { bool survStageDens; bool disperseOnLoss; // individuals disperse on complete loss of patch // (otherwise they die) + bool fecSpatial, devSpatial, survSpatial; short habDimK; // dimension of carrying capacities matrix float* habK; // habitat-specific carrying capacities (inds/cell) float devCoeff; // density-dependent development coefficient @@ -563,10 +567,13 @@ class Species { float** ddwtDev; // density-dependent weights matrix for development float** ddwtSurv; // density-dependent weights matrix for survival // NB for the following arrays, sex 0 is females, sex 1 is males - float fec[NSTAGES][NSEXES]; // fecundities - float dev[NSTAGES][NSEXES]; // development probabilities - float surv[NSTAGES][NSEXES]; // survival probabilities - short minAge[NSTAGES][NSEXES]; // minimum age to enter stage + float fec[gMaxNbStages][gMaxNbSexes]; // fecundities + float dev[gMaxNbStages][gMaxNbSexes]; // development probabilities + float surv[gMaxNbStages][gMaxNbSexes]; // survival probabilities + short minAge[gMaxNbStages][gMaxNbSexes]; // minimum age to enter stage + int fecLayer[gMaxNbStages][gMaxNbSexes]; // layer for spatial varying fecundity + int devLayer[gMaxNbStages][gMaxNbSexes]; // layer for spatial varying development + int survLayer[gMaxNbStages][gMaxNbSexes]; // layer for spatial varying survival // NOTE - IN THEORY, NEXT 3 VARIABLES COULD BE COMMON, BUT WE WOULD NEED TO ENSURE THAT // ALL MATRICES ARE DELETED IF THERE IS A CHANGE IN NO. OF STAGES OR REPRODUCTION TYPE // ***** TO BE RECONSIDERED LATER ***** @@ -578,24 +585,18 @@ class Species { // genome parameters - short nTraits; // no. of inheritable traits - short emigTrait[2]; // to record first and no. of emigration traits - short movtTrait[2]; // to record first and no. of transfer traits - short settTrait[2]; // to record first and no. of settlement traits + /**The traits table.*/ + std::map> spTraitTable; + std::set chromosomeEnds; + int genomeSize; bool diploid; - bool neutralMarkers; // neutral markers in absence of any adaptive traits - bool pleiotropic; - bool trait1Chromosome; // 1:1 mapping of chromosome to trait - short nChromosomes; // no. of chromosomes - double probMutn; // allelic mutation probability - double probCrossover; // crossover probability at meiosis - double alleleSD; // s.d. of initial allelic values around phenotypic value - double mutationSD; // s.d. of mutation magnitude - short nNLoci; // no. of nLoci set - short* nLoci; // no. of loci per chromosome - short nTraitNames; // no. of trait names set - traitData* traitdata; // for mapping of chromosome loci to traits - string* traitnames; // trait names for parameter output + bool mutationsOn; + int nbGeneticFitnessTraits; + float recombinationRate; + std::set samplePatchList; + int nPatchesToSample; //for cell based landscape + std::set stagesToSampleFrom; + string nIndsToSample; //could be integer or 'all', all means in in selected patches not necessarily all in population // emigration parameters @@ -606,49 +607,27 @@ class Species { short emigStage; // stage which emigrates (used for stage-strucutred population // having individual variability in emigration probability) // NB for the following arrays, sex 0 is females, sex 1 is males - float d0[NSTAGES][NSEXES]; // maximum emigration probability - float alphaEmig[NSTAGES][NSEXES]; // slope of density-dependent reaction norm - float betaEmig[NSTAGES][NSEXES]; // inflection point of reaction norm (in terms of N/K) + float d0[gMaxNbStages][gMaxNbSexes]; // maximum emigration probability + float alphaEmig[gMaxNbStages][gMaxNbSexes]; // slope of density-dependent reaction norm + float betaEmig[gMaxNbStages][gMaxNbSexes]; // inflection point of reaction norm (in terms of N/K) // NB Initialisation parameters are made double to avoid conversion errors (reason unclear) // on traits maps using FloatToStr() - // As evolving traits are not stage-dependent, no. of rows can be 1 - // Indeed, they could be 1-D arrays - double d0Mean[1][NSEXES]; - double d0SD[1][NSEXES]; - double alphaMean[1][NSEXES]; - double alphaSD[1][NSEXES]; - double betaMean[1][NSEXES]; - double betaSD[1][NSEXES]; - double d0Scale; // scaling factor for d0 - double alphaScale; // scaling factor for alpha - double betaScale; // scaling factor for beta // transfer parameters - bool moveModel; + bool usesMovtProcess; bool stgDepTrfr; bool sexDepTrfr; bool distMort; bool indVarTrfr; bool twinKern; bool habMort; // habitat-dependent mortality - float meanDist1[NSTAGES][NSEXES]; // mean of 1st dispersal kernel (m) - float meanDist2[NSTAGES][NSEXES]; // mean of 2nd dispersal kernel (m) - float probKern1[NSTAGES][NSEXES]; // probability of dispersing with the 1st kernel + float meanDist1[gMaxNbStages][gMaxNbSexes]; // mean of 1st dispersal kernel (m) + float meanDist2[gMaxNbStages][gMaxNbSexes]; // mean of 2nd dispersal kernel (m) + float probKern1[gMaxNbStages][gMaxNbSexes]; // probability of dispersing with the 1st kernel // NB INITIAL limits are made double to avoid conversion errors (reason unclear) // on traits maps using FloatToStr() // As evolving traits are are not stage-dependent, no. of rows can be 1 - // Indeed, as they are INITIAL limits, which may subsequently be exceeded, they could be - // 1-D arrays - double dist1Mean[1][NSEXES]; // mean of initial mean of the 1st dispersal kernel (m) - double dist1SD[1][NSEXES]; // s.d. of initial mean of the 1st dispersal kernel (m) - double dist2Mean[1][NSEXES]; // mean of initial mean of the 2nd dispersal kernel (m) - double dist2SD[1][NSEXES]; // s.d. of initial mean of the 2nd dispersal kernel (m) - double PKern1Mean[1][NSEXES]; // mean of initial prob. of dispersing with 1st kernel - double PKern1SD[1][NSEXES]; // s.d. of initial prob. of dispersing with 1st kernel - float dist1Scale; // scaling factor for mean of 1st dispersal kernel (m) - float dist2Scale; // scaling factor for mean of 2nd dispersal kernel (m) - float PKern1Scale; // scaling factor for prob. of dispersing with 1st kernel float fixedMort; // constant mortality probability float mortAlpha; // slope for mortality distance dependence function float mortBeta; // inflection point for mortality distance dependence function @@ -666,28 +645,10 @@ class Species { double* habStepMort; // habitat-dependent per-step mortality probability float stepLength; // CRW step length (m) float rho; // CRW correlation coefficient - double dpMean[1][NSEXES]; // mean of initial SMS directional persistence - double dpSD[1][NSEXES]; // s.d. of initial SMS directional persistence - double gbMean[1][NSEXES]; // mean of initial SMS goal bias - double gbSD[1][NSEXES]; // s.d. of initial SMS goal bias - double alphaDBMean[1][NSEXES]; // mean of initial SMS dispersal bias decay rate - double alphaDBSD[1][NSEXES]; // s.d. of initial SMS dispersal bias decay rate - double betaDBMean[1][NSEXES]; // mean of initial SMS dispersal bias decay infl. pt. - double betaDBSD[1][NSEXES]; // s.d. of initial SMS dispersal bias decay infl. pt. - float dpScale; // scaling factor for SMS directional persistence - float gbScale; // scaling factor for SMS goal bias - float alphaDBScale; // scaling factor for SMS dispersal bias decay rate - float betaDBScale; // scaling factor for SMS dispersal bias decay infl. pt. - double stepLgthMean[1][NSEXES]; // mean of initial step length (m) - double stepLgthSD[1][NSEXES]; // s.d. of initial step length (m) - double rhoMean[1][NSEXES]; // mean of initial correlation coefficient - double rhoSD[1][NSEXES]; // s.d. of initial correlation coefficient - float stepLScale; // scaling factor for step length (m) - float rhoScale; // scaling factor for correlation coefficient short habDimTrfr; // dimension of habitat-dependent step mortality and costs matrices int* habCost; // habitat costs bool costMap; // import cost map from file? - bool straigtenPath; // straighten path after decision not to settle + bool straightenPath; // straighten path after decision not to settle bool fullKernel; // used to indicate special case when density-independent emigration // is 1.0, and kernel-based movement within the natal cell is used // to determine philopatry @@ -697,52 +658,31 @@ class Species { bool stgDepSett; bool sexDepSett; bool indVarSett; // individual variation in settlement - bool densDepSett[NSTAGES][NSEXES]; - bool wait[NSTAGES][NSEXES]; // wait to continue moving next season (stage-structured model only) - bool go2nbrLocn[NSTAGES][NSEXES]; // settle in neighbouring cell/patch if available (ditto) - bool findMate[NSTAGES][NSEXES]; - int minSteps[NSTAGES][NSEXES]; // minimum no. of steps - int maxSteps[NSTAGES][NSEXES]; // maximum total no. of steps - int maxStepsYr[NSTAGES][NSEXES]; // maximum no. of steps in any one dispersal period - float s0[NSTAGES][NSEXES]; // maximum settlement probability - float alphaS[NSTAGES][NSEXES]; // slope of the settlement reaction norm to density - float betaS[NSTAGES][NSEXES]; // inflection point of the settlement reaction norm to density - double s0Mean[1][NSEXES]; // mean of initial maximum settlement probability - double s0SD[1][NSEXES]; // s.d. of initial maximum settlement probability - double alphaSMean[1][NSEXES]; // mean of initial settlement reaction norm slope - double alphaSSD[1][NSEXES]; // s.d. of initial settlement reaction norm slope - double betaSMean[1][NSEXES]; // mean of initial settlement reaction norm inflection point - double betaSSD[1][NSEXES]; // s.d. of initial settlement reaction norm inflection point - float s0Scale; // scaling factor for maximum settlement probability - float alphaSScale; // scaling factor for settlement reaction norm slope - float betaSScale; // scaling factor for settlement reaction norm inflection point + bool densDepSett[gMaxNbStages][gMaxNbSexes]; + bool wait[gMaxNbStages][gMaxNbSexes]; // wait to continue moving next season (stage-structured model only) + bool go2nbrLocn[gMaxNbStages][gMaxNbSexes]; // settle in neighbouring cell/patch if available (ditto) + bool findMate[gMaxNbStages][gMaxNbSexes]; + int minSteps[gMaxNbStages][gMaxNbSexes]; // minimum no. of steps + int maxSteps[gMaxNbStages][gMaxNbSexes]; // maximum total no. of steps + int maxStepsYr[gMaxNbStages][gMaxNbSexes]; // maximum no. of steps in any one dispersal period + float s0[gMaxNbStages][gMaxNbSexes]; // maximum settlement probability + float alphaS[gMaxNbStages][gMaxNbSexes]; // slope of the settlement reaction norm to density + float betaS[gMaxNbStages][gMaxNbSexes]; // inflection point of the settlement reaction norm to density // other attributes - int spNum; }; -/* IMPORTANT NOTE: -At the time of writing (24/4/14) the stage- and sex-dependent parameters for emigration -and dispersal (e.g. d0[NSTAGES][NSEXES]) are set according to the appropriate stage- and -sex-dependent settings; thus a species could be structured and sexual, but parameter values -are set for elements [0][0] only if emigration/transfer is stage- and sex-independent. -However, the parameters for settlement are set for ALL stages and sexes appropriate to the -species, regardless of settlement dependency. The rationale for this is that settlement -parameters need to be accessed many more times for a movement model (at each step) than -emigration or transfer parameters, and therefore there will be a performance gain in -avoiding nested if statements in Individual::moveStep() which would otherwise be required -to get the correct parameter values from the settlement arrays. Whether that particular -rationale is justified remains to be tested! -*/ //--------------------------------------------------------------------------- -#if RSDEBUG -//extern ofstream DEBUGLOG; -extern void DebugGUI(string); -#endif +#ifdef UNIT_TESTS +// For testing purposes only +Species* createDefaultSpecies(); +demogrParams createDefaultHaploidDemogrParams(); +demogrParams createDefaultDiploidDemogrParams(); +#endif // UNIT_TESTS //--------------------------------------------------------------------------- #endif diff --git a/SpeciesTrait.cpp b/SpeciesTrait.cpp new file mode 100644 index 0000000..418c222 --- /dev/null +++ b/SpeciesTrait.cpp @@ -0,0 +1,299 @@ + +#include "SpeciesTrait.h" + +// Species trait constructor +SpeciesTrait::SpeciesTrait( + const TraitType& trType, + const sex_t& sx, + const set& pos, + const ExpressionType& expr, + const set& initialPositions, + const DistributionType& initDist, + const map initParams, + const DistributionType& initDomDist, + const map initDomParams, + bool isInherited, + const float& mutRate, + const DistributionType& mutationDist, + const map mutationParams, + const DistributionType& dominanceDist, + const map dominanceParams, + const int nPloidy, + const bool isOutput) : + traitType{ trType }, + sex{ sx }, + genePositions{ pos }, + expressionType{ expr }, + initPositions{ initialPositions }, + initialDistribution{ initDist }, + initialParameters{ initParams }, + initialDomDistribution{ initDomDist }, + initialDomParameters{ initDomParams }, + dominanceDistribution{ dominanceDist }, + dominanceParameters{ dominanceParams }, + inherited{ isInherited }, + mutationDistribution{ mutationDist }, + mutationParameters{ mutationParams }, + mutationRate{ mutRate }, + ploidy{ nPloidy }, + traitIsOutput{ isOutput } +{ + // Check distribution parameters + // Initial allele distribution + for (auto [paramType, paramVal] : initParams) { + switch (paramType) + { + case MIN: case MAX: case MEAN: + if (!isValidTraitVal(paramVal)) + throw logic_error("Invalid parameter value: initial parameter " + to_string(paramType) + " must have a valid value for trait " + to_string(traitType) + "."); + break; + case SD: + if (paramVal <= 0.0) + throw logic_error("Invalid parameter value: initial parameter " + to_string(paramType) + " must be strictly positive"); + break; + default: + break; + } + } + + // Initial dominance distribution + for (auto [paramType, paramVal] : initDomParams) { + switch (paramType) + { + case MIN: case MAX: case MEAN: + if (paramVal < 0.0) + throw logic_error("Invalid parameter value: initial dominance parameter " + to_string(paramType) + " must not be negative."); + break; + case SD: case SHAPE: case SCALE: + if (paramVal <= 0.0) + throw logic_error("Invalid parameter value: initial dominance parameter " + to_string(paramType) + " must be strictly positive"); + break; + default: + break; + } + } + + // Mutation distribution + for (auto [paramType, paramVal] : mutationParams) { + switch (paramType) + { + case MIN: case MAX: case MEAN: + if ( + (trType == NEUTRAL || trType == GENETIC_LOAD || trType == GENETIC_LOAD1 || + trType == GENETIC_LOAD2 || trType == GENETIC_LOAD3 || trType == GENETIC_LOAD4 || trType == GENETIC_LOAD5) + && !isValidTraitVal(paramVal) + // dispersal traits are cumulative so no value is invalid + ) + throw logic_error("Invalid parameter value: mutation parameter " + to_string(paramType) + " must have a valid value for trait " + to_string(traitType) + "."); + break; + case SD: case SHAPE: case SCALE: + if (paramVal <= 0.0) + throw logic_error("Invalid parameter value: mutation parameter " + to_string(paramType) + " must be strictly positive"); + break; + default: + break; + } + } + + // Dominance distribution + for (auto [paramType, paramVal] : dominanceParams) { + switch (paramType) + { + case MIN: case MAX: case MEAN: + if (paramVal < 0.0) + throw logic_error("Invalid parameter value: dominance parameter " + to_string(paramType) + " must not be negative."); + break; + case SD: case SHAPE: case SCALE: + if (paramVal <= 0.0) + throw logic_error("Invalid parameter value: dominance parameter " + to_string(paramType) + " must be strictly positive"); + break; + default: + break; + } + } +} + +bool SpeciesTrait::isValidTraitVal(const float& val) const { + switch (traitType) + { + // Neutral trait + case NEUTRAL: // only need to check for input parameters + { + return val >= 0.0 && val <= 255.0; + } + // Genetic Load + case GENETIC_LOAD: case GENETIC_LOAD1: case GENETIC_LOAD2: case GENETIC_LOAD3: case GENETIC_LOAD4: case GENETIC_LOAD5: + { + return val >= -1.0 // genetic fitness traits can be beneficial + && val <= 1.0; + break; + } + // Dispersal traits + /// Emigration + case E_D0_F: case E_D0_M: case E_D0: { + return val >= 0.0 && val <= 1.0; // is a probability + break; + } + case E_ALPHA_F: case E_ALPHA_M: case E_ALPHA: + case E_BETA_F: case E_BETA_M: case E_BETA: + { + return true; // slope and inflexion point can be any value + break; + } + /// Settlement + case S_S0_F: case S_S0_M: case S_S0: + { + return val >= 0.0 && val <= 1.0; + break; + } + case S_ALPHA_F: case S_ALPHA_M: case S_ALPHA: + case S_BETA_F: case S_BETA_M: case S_BETA: + { + return true; // slope and inflection point can be any value + break; + } + /// Transfer - Kernels + case KERNEL_MEANDIST_1_F: case KERNEL_MEANDIST_1_M: case KERNEL_MEANDIST_1: + case KERNEL_MEANDIST_2_F: case KERNEL_MEANDIST_2_M: case KERNEL_MEANDIST_2: + { + return val >= 0.0; // is a distance + break; + } + case KERNEL_PROBABILITY_F: case KERNEL_PROBABILITY_M: case KERNEL_PROBABILITY: + { + return val >= 0.0 && val <= 1.0; + break; + } + /// Transfer - Correlated random walk + case CRW_STEPLENGTH: + { + return val >= 0.0; + break; + } + case CRW_STEPCORRELATION: + { + return val >= 0.0 && val <= 1.0; + break; + } + /// Transfer - Stochastic Movement Simulator + case SMS_DP: + { + return val >= 1.0; // according to parameter doc + break; + } + case SMS_GB: + { + return val >= 1.0; // according to parameter doc + break; + } + case SMS_ALPHADB: + { + return val > 0.0; + break; + } + case SMS_BETADB: + { + return true; + break; + } + default: + throw logic_error("Invalid trait type " + to_string(traitType) + " passed to isValidTraitVal()."); + break; + } +} + +#ifdef UNIT_TESTS // Testing only + +// Create a default set of gene positions ranging from zero to genome size +set createTestGenePositions(const int genomeSz) { + set genePositions; + for (int i = 0; i < genomeSz; i++) genePositions.insert(i); + return genePositions; +} + +SpeciesTrait* createTestEmigSpTrait(const set& genePositions, const bool& isDiploid) { + // Create species trait + const map distParams{ + pair{GenParamType::MIN, 0.0}, + pair{GenParamType::MAX, 1.0} + }; + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::E_D0, + sex_t::NA, + genePositions, + ExpressionType::ADDITIVE, + genePositions, + DistributionType::UNIFORM, + distParams, + DistributionType::NONE, // no dominance + distParams, + true, // isInherited + 0.0, // mutation rate + DistributionType::UNIFORM, + distParams, + DistributionType::NONE, // no dominance + distParams, + isDiploid ? 2 : 1, + false + ); + return spTr; +} + +SpeciesTrait* createTestGenLoadTrait(const set& genePositions, const bool& isDiploid) { + // Create species trait + const map distParams{ + pair{GenParamType::MIN, 0.0}, + pair{GenParamType::MAX, 1.0} + }; + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::GENETIC_LOAD, + sex_t::NA, + genePositions, + ExpressionType::MULTIPLICATIVE, + genePositions, + DistributionType::NONE, + distParams, + DistributionType::NONE, // initialise dominance to zero + distParams, + true, // isInherited + 0.0, // mutation rate + DistributionType::UNIFORM, + distParams, + DistributionType::UNIFORM, + distParams, + isDiploid ? 2 : 1, + false + ); + return spTr; +} + +SpeciesTrait* createTestNeutralSpTrait(const float& maxAlleleVal, const set& genePositions, const bool& isDiploid) { + + const map distParams{ + // Set max allele value + pair{GenParamType::MAX, maxAlleleVal} + }; + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::NEUTRAL, + sex_t::NA, + genePositions, + ExpressionType::NOTEXPR, + genePositions, + // Sample initial values from uniform(0, max) + DistributionType::UNIFORM, distParams, + DistributionType::NONE, // No dominance + map{}, + true, // isInherited + 0.0, // mutation rate + // Mutation sampled from a uniform(0, max) + DistributionType::KAM, + distParams, + DistributionType::NONE, // No dominance + map{}, + isDiploid ? 2 : 1, + false + ); + return spTr; +} + +#endif // UNIT_TESTS diff --git a/SpeciesTrait.h b/SpeciesTrait.h new file mode 100644 index 0000000..69b1313 --- /dev/null +++ b/SpeciesTrait.h @@ -0,0 +1,106 @@ +#ifndef SPECIESTRAITH +#define SPECIESTRAITH + +#include "Parameters.h" +#include "Species.h" + +#include +#include +#include +#include + +class Species; // forward declaration to overcome circularity issue + +// Species-level traits +// Features of traits that are shared across all individuals in the same species +class SpeciesTrait { +public: + SpeciesTrait( + const TraitType& traitType, + const sex_t& sex, + const set& pos, + const ExpressionType& expr, + const set& initialPositions, + const DistributionType& initDist, + const map initParams, + const DistributionType& initDomDist, + const map initDomParams, + bool isInherited, + const float& mutationRate, + const DistributionType& mutationDist, + const map mutationParams, + const DistributionType& dominanceDist, + const map dominanceParams, + const int ploidy, + const bool isOutput + ); + + bool isValidTraitVal(const float& val) const; + TraitType getTraitType() const { return traitType; } + bool isOutput() const { return traitIsOutput; } + + // Getters + sex_t getSex() const { return sex; } + float getMutationRate() const { return mutationRate; } + short getPloidy() const { return ploidy; } + set& getGenePositions() { return genePositions; } // returning by reference, make sure receiver is const + set getInitPositions() const { return initPositions; } + int getPositionsSize() const { return static_cast(genePositions.size()); } + bool isInherited() const { return inherited; } + + DistributionType getInitialDistribution() const { return initialDistribution; }; + map getInitialParameters() const { return initialParameters; }; + DistributionType getInitDomDistribution() const { return initialDomDistribution; }; + map getInitDomParameters() const { return initialDomParameters; }; + DistributionType getMutationDistribution() const { return mutationDistribution; }; + map getMutationParameters() const { return mutationParameters; }; + DistributionType getDominanceDistribution() const { return dominanceDistribution; }; + map getDominanceParameters() const { return dominanceParameters; }; + + ExpressionType getExpressionType() const { return expressionType; }; + + int getNbNeutralAlleles() const { + if (!traitType == NEUTRAL) throw logic_error("getNbNeutralAlleles() should only be called for neutral traits."); + else { + int maxAlleleVal = max( + getMutationParameters().find(MAX)->second + 1, + getInitialParameters().find(MAX)->second + 1 + ); // possible values range from 0 to MAX + return maxAlleleVal; + } + } + +private: + + int ploidy; + float mutationRate; + TraitType traitType; + bool traitIsOutput; + sex_t sex; + + // Positions in the genome of all genes (loci) pertaining to this trait + // The genome itself is not modelled explicitly + set genePositions; + set initPositions; + + ExpressionType expressionType; + DistributionType initialDistribution; + map initialParameters; + DistributionType initialDomDistribution; + map initialDomParameters; + DistributionType dominanceDistribution; + map dominanceParameters; + bool inherited; + DistributionType mutationDistribution; + map mutationParameters; +}; + +#ifdef UNIT_TESTS // Testing only +// Create a default set of gene positions ranging from zero to genome size +set createTestGenePositions(const int genomeSz); +SpeciesTrait* createTestEmigSpTrait(const set& genePositions, const bool& isDiploid); +SpeciesTrait* createTestGenLoadTrait(const set& genePositions, const bool& isDiploid); +SpeciesTrait* createTestNeutralSpTrait(const float& maxAlleleVal, const set& genePositions, const bool& isDiploid); +#endif // UNIT_TESTS + +#endif // SPECIESTRAITH diff --git a/SubCommunity.cpp b/SubCommunity.cpp index d908892..94b00c4 100644 --- a/SubCommunity.cpp +++ b/SubCommunity.cpp @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -33,13 +33,13 @@ SubCommunity::SubCommunity(Patch* pPch, int num) { subCommNum = num; pPatch = pPch; // record the new sub-community no. in the patch - pPatch->setSubComm((intptr)this); + pPatch->setSubComm(this); initial = false; occupancy = 0; } SubCommunity::~SubCommunity() { - pPatch->setSubComm(0); + pPatch->setSubComm(nullptr); int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { // all populations delete popns[i]; @@ -48,7 +48,7 @@ SubCommunity::~SubCommunity() { if (occupancy != 0) delete[] occupancy; } -intptr SubCommunity::getNum(void) { return subCommNum; } +int SubCommunity::getNum(void) { return subCommNum; } Patch* SubCommunity::getPatch(void) { return pPatch; } @@ -64,6 +64,7 @@ void SubCommunity::initialise(Landscape* pLandscape, Species* pSpecies) int ncells; landParams ppLand = pLandscape->getLandParams(); initParams init = paramsInit->getInit(); + // determine size of initial population int nInds = 0; if (subCommNum == 0 // matrix patch @@ -92,23 +93,23 @@ void SubCommunity::initialise(Landscape* pLandscape, Species* pSpecies) } else nInds = 0; } + // create new population only if it is non-zero or the matrix popn if (subCommNum == 0 || nInds > 0) { newPopn(pLandscape, pSpecies, pPatch, nInds); } + } // initialise a specified individual void SubCommunity::initialInd(Landscape* pLandscape, Species* pSpecies, Patch* pPatch, Cell* pCell, int ix) { - - demogrParams dem = pSpecies->getDemogr(); - stageParams sstruct = pSpecies->getStage(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + demogrParams dem = pSpecies->getDemogrParams(); + stageParams sstruct = pSpecies->getStageParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); - genomeData gen = pSpecies->getGenomeData(); short stg, age, repInt; Individual* pInd; float probmale; @@ -133,19 +134,15 @@ void SubCommunity::initialInd(Landscape* pLandscape, Species* pSpecies, else { if (iind.sex == 1) probmale = 1.0; else probmale = 0.0; } - pInd = new Individual(pCell, pPatch, stg, age, repInt, probmale, trfr.moveModel, trfr.moveType); + pInd = new Individual(pSpecies, pCell, pPatch, stg, age, repInt, probmale, trfr.usesMovtProc, trfr.moveType); - // add new individual to the population - // NB THIS WILL NEED TO BE CHANGED FOR MULTIPLE SPECIES... - popns[0]->recruit(pInd); - - if (emig.indVar || trfr.indVar || sett.indVar || gen.neutralMarkers) - { + if (pSpecies->getNTraits() > 0) { // individual variation - set up genetics landData land = pLandscape->getLandData(); - pInd->setGenes(pSpecies, land.resol); + pInd->setUpGenes(pSpecies, land.resol); } + popns[0]->recruit(pInd); } // Create a new population, and return its address @@ -165,7 +162,7 @@ popStats SubCommunity::getPopStats(void) { // FOR SINGLE SPECIES IMPLEMENTATION, THERE IS ONLY ONE POPULATION IN THE PATCH int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { // all populations - pop = popns[i]->getStats(); + pop = popns[i]->getStats(pPatch->getDemoScaling()); p.pSpecies = pop.pSpecies; p.spNum = pop.spNum; p.nInds += pop.nInds; @@ -228,9 +225,9 @@ void SubCommunity::patchChange(void) { if (localK <= 0.0) { // patch in dynamic landscape has become unsuitable for (int i = 0; i < npops; i++) { // all populations pSpecies = popns[i]->getSpecies(); - demogrParams dem = pSpecies->getDemogr(); + demogrParams dem = pSpecies->getDemogrParams(); if (dem.stageStruct) { - stageParams sstruct = pSpecies->getStage(); + stageParams sstruct = pSpecies->getStageParams(); if (sstruct.disperseOnLoss) popns[i]->allEmigrate(); else popns[i]->extirpate(); } @@ -245,6 +242,7 @@ void SubCommunity::reproduction(int resol, float epsGlobal, short rasterType, bo { if (subCommNum == 0) return; // no reproduction in the matrix float localK, envval; + std::vector localDemoScaling; Cell* pCell; envGradParams grad = paramsGrad->getGradient(); envStochParams env = paramsStoch->getStoch(); @@ -254,6 +252,7 @@ void SubCommunity::reproduction(int resol, float epsGlobal, short rasterType, bo if (npops < 1) return; localK = pPatch->getK(); + localDemoScaling = pPatch->getDemoScaling(); if (localK > 0.0) { if (patchModel) { envval = 1.0; // environmental gradient is currently not applied for patch-based model @@ -278,7 +277,7 @@ void SubCommunity::reproduction(int resol, float epsGlobal, short rasterType, bo } } for (int i = 0; i < npops; i++) { // all populations - popns[i]->reproduction(localK, envval, resol); + popns[i]->reproduction(localK, envval, resol, localDemoScaling); popns[i]->fledge(); } } @@ -287,11 +286,9 @@ void SubCommunity::reproduction(int resol, float epsGlobal, short rasterType, bo void SubCommunity::emigration(void) { if (subCommNum == 0) return; // no emigration from the matrix - float localK; - int npops = (int)popns.size(); - // THE FOLLOWING MAY BE MORE EFFICIENT WHILST THERE IS ONLY ONE SPECIES ... + int npops = static_cast(popns.size()); if (npops < 1) return; - localK = pPatch->getK(); + float localK = pPatch->getK(); // NOTE that even if K is zero, it could have been >0 in previous time-step, and there // might be emigrants if there is non-juvenile emigration for (int i = 0; i < npops; i++) { // all populations @@ -299,25 +296,44 @@ void SubCommunity::emigration(void) } } -// Remove emigrants from their natal patch and add to patch 0 (matrix) -void SubCommunity::initiateDispersal(SubCommunity* matrix) { +// Remove emigrants from their natal patch and add to a map of vectors +void SubCommunity::recruitDispersers(std::map>& disperserPool) { if (subCommNum == 0) return; // no dispersal initiation in the matrix popStats pop; disperser disp; int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { // all populations - pop = popns[i]->getStats(); + pop = popns[i]->getStats(pPatch->getDemoScaling()); + Species* pSpecies = popns[i]->getSpecies(); for (int j = 0; j < pop.nInds; j++) { disp = popns[i]->extractDisperser(j); if (disp.yes) { // disperser - has already been removed from natal population - // add to matrix population - matrix->recruit(disp.pInd, pop.pSpecies); + disperserPool[pSpecies].push_back(disp.pInd); } } // remove pointers to emigrants popns[i]->clean(); } +} + +// Add all individuals in the matrix to the disperser pool +void SubCommunity::disperseMatrix(std::map> &inds_map) { + if (subCommNum != 0) return; + popStats pop; + + int npops = (int)popns.size(); + for (int i = 0; i < npops; i++) { + pop = popns[i]->getStats(pPatch->getDemoScaling()); + Species* pSpecies = popns[i]->getSpecies(); +#pragma omp for schedule(static) + for (int j = 0; j < pop.nInds; j++) { + Individual *pInd = popns[i]->extractIndividual(j); + inds_map[pSpecies].push_back(pInd); +} +#pragma omp single + popns[i]->clean(); + } } @@ -331,99 +347,408 @@ void SubCommunity::recruit(Individual* pInd, Species* pSpecies) { } } -// Transfer through the matrix - run for the matrix sub-community only -#if RS_RCPP -int SubCommunity::transfer(Landscape* pLandscape, short landIx, short nextseason) -#else -int SubCommunity::transfer(Landscape* pLandscape, short landIx) -#endif // RS_RCPP -{ - int ndispersers = 0; +// Add individuals into the local population of their species in the patch +void SubCommunity::recruitMany(std::vector& inds, Species* pSpecies) { int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { // all populations + if (pSpecies == popns[i]->getSpecies()) { + popns[i]->recruitMany(inds); + } + } +} + +// Transfer through the matrix - run for a per-species map of vectors of individuals +int SubCommunity::resolveTransfer(std::map>& dispersingInds, Landscape* pLandscape, short landIx) +{ + int nbStillDispersing = 0; + int disperser; + Patch* pPatch = nullptr; + Cell* pCell = nullptr; + simParams sim = paramsSim->getSim(); + + for (auto & it : dispersingInds) { // all species + + Species* const& pSpecies = it.first; + short reptype = pSpecies->getRepType(); + transferRules trfr = pSpecies->getTransferRules(); + + vector& inds = it.second; + + // each individual takes one step + // for dispersal by kernel, this should be the only step taken + for (auto& pInd : inds) { + if (trfr.usesMovtProc) { + disperser = pInd->moveStep(pLandscape, pSpecies, landIx, sim.absorbing); + } + else { + disperser = pInd->moveKernel(pLandscape, pSpecies, sim.absorbing); + } + nbStillDispersing += disperser; + if (disperser) { + if (reptype > 0) + { // sexual species - record as potential settler in new patch + if (pInd->getStatus() == 2) + { // disperser has found a patch + pCell = pInd->getCurrCell(); + pPatch = pCell->getPatch(); + if (pPatch != nullptr) { // not no-data area + pPatch->incrPossSettler(pSpecies, pInd->getSex()); + } + } + } + } + } + } + return nbStillDispersing; +} + + +// Determine whether there is a potential mate present in a patch which a potential +// settler has reached +bool SubCommunity::matePresent(Species* pSpecies, Cell* pCell, short othersex) +{ + Patch* pPatch; + Population* pNewPopn; + int popsize = 0; + bool matefound = false; + + pPatch = pCell->getPatch(); + if (pPatch != nullptr) { + if (pPatch->getPatchNum() > 0) { // not the matrix patch + if (pPatch->getK() > 0.0) + { // suitable + pNewPopn = pPatch->getPopn(pSpecies); + if (pNewPopn != nullptr) { + const stageParams sstruct = pSpecies->getStageParams(); + // count members of other sex already resident in the patch + for (int stg = 0; stg < sstruct.nStages; stg++) { + popsize += pNewPopn->getNbInds(stg, othersex); + } + } + if (popsize < 1) { + // add any potential settlers of the other sex + popsize += pPatch->getPossSettlers(pSpecies, othersex); + } + } + } + } + if (popsize > 0) matefound = true; + return matefound; +} + +// Transfer is run for populations in the matrix only +#if RS_RCPP // included also SEASONAL +int SubCommunity::resolveSettlement(std::map>& dispersingInds, Landscape* pLandscape, short nextseason) +#else +int SubCommunity::resolveSettlement(std::map>& dispersingInds, Landscape* pLandscape) +#endif +{ + int nbStillDispersing = 0; + short othersex; + bool mateOK, densdepOK; + int patchnum; + double localK, popsize, settprob; + Patch* pPatch = 0; + Cell* pCell = 0; + indStats ind; + Population* pNewPopn = 0; + locn newloc, nbrloc; + + landData ppLand = pLandscape->getLandData(); + settleRules sett; + settleTraits settDD; + settlePatch settle; + simParams sim = paramsSim->getSim(); + + for (auto& it : dispersingInds) { // all species + Species* const& pSpecies = it.first; + transferRules trfr = pSpecies->getTransferRules(); + settleType settletype = pSpecies->getSettle(); + + vector& inds = it.second; + + // each individual which has reached a potential patch decides whether to settle + for (auto& pInd : inds) { + ind = pInd->getStats(); + if (ind.sex == 0) othersex = 1; else othersex = 0; + if (settletype.stgDep) { + if (settletype.sexDep) sett = pSpecies->getSettRules(ind.stage, ind.sex); + else sett = pSpecies->getSettRules(ind.stage, 0); + } + else { + if (settletype.sexDep) sett = pSpecies->getSettRules(0, ind.sex); + else sett = pSpecies->getSettRules(0, 0); + } + if (ind.status == 2) + { // awaiting settlement + pCell = pInd->getCurrCell(); + if (pCell == 0) { + // this condition can occur in a patch-based model at the time of a dynamic landscape + // change when there is a range restriction in place, since a patch can straddle the + // range restriction and an individual forced to disperse upon patch removal could + // start its trajectory beyond the boundary of the restrictyed range - such a model is + // not good practice, but the condition must be handled by killing the individual conceerned + ind.status = 6; + } + else { + mateOK = false; + if (sett.findMate) { + // determine whether at least one individual of the opposite sex is present in the + // new population + if (matePresent(pSpecies, pCell, othersex)) mateOK = true; + } + else { // no requirement to find a mate + mateOK = true; + } + + densdepOK = false; + settle = pInd->getSettPatch(); + if (sett.densDep) + { + pPatch = pCell->getPatch(); + if (pPatch != nullptr) { // not no-data area + if (settle.settleStatus == 0 + || settle.pSettPatch != pPatch) + // note: second condition allows for having moved from one patch to another + // adjacent one + { + // determine whether settlement occurs in the (new) patch + localK = (double)pPatch->getK(); + pNewPopn = pPatch->getPopn(pSpecies); + if (pNewPopn == nullptr) { // population has not been set up in the new patch + popsize = 0.0; + } + else { + popsize = (double)pNewPopn->getNbInds(); + } + if (localK > 0.0) { + // make settlement decision + if (settletype.indVar) settDD = pInd->getIndSettTraits(); #if RS_RCPP - ndispersers += popns[i]->transfer(pLandscape, landIx, nextseason); + else settDD = pSpecies->getSpSettTraits(ind.stage, ind.sex); #else - ndispersers += popns[i]->transfer(pLandscape, landIx); -#endif // RS_RCPP + else { + if (settletype.sexDep) { + if (settletype.stgDep) + settDD = pSpecies->getSpSettTraits(ind.stage, ind.sex); + else + settDD = pSpecies->getSpSettTraits(0, ind.sex); + } + else { + if (settletype.stgDep) + settDD = pSpecies->getSpSettTraits(ind.stage, 0); + else + settDD = pSpecies->getSpSettTraits(0, 0); + } + } +#endif //RS_RCPP + settprob = settDD.s0 / + (1.0 + exp(-(popsize / localK - (double)settDD.beta) * (double)settDD.alpha)); + + if (pRandom->Bernoulli(settprob)) { // settlement allowed + densdepOK = true; + settle.settleStatus = 2; + } + else { // settlement procluded + settle.settleStatus = 1; + } + settle.pSettPatch = pPatch; + } + pInd->setSettPatch(settle); + } + else { + if (settle.settleStatus == 2) { // previously allowed to settle + densdepOK = true; + } + } + } + } + else { // no density-dependent settlement + densdepOK = true; + settle.settleStatus = 2; + settle.pSettPatch = pPatch; + pInd->setSettPatch(settle); + } - } - return ndispersers; + if (mateOK && densdepOK) { // can recruit to patch + ind.status = 4; + nbStillDispersing--; + } + else { // does not recruit + if (trfr.usesMovtProc) { + ind.status = 1; // continue dispersing, unless ... + // ... maximum steps has been exceeded + pathSteps steps = pInd->getSteps(); + settleSteps settsteps = pSpecies->getSteps(ind.stage, ind.sex); + if (steps.year >= settsteps.maxStepsYr) { + ind.status = 3; // waits until next year + } + if (steps.total >= settsteps.maxSteps) { + ind.status = 6; // dies + } + } + else { // dispersal kernel + if (sett.wait) { + ind.status = 3; // wait until next dispersal event + } + else { + ind.status = 6; // (dies unless a neighbouring cell is suitable) + } + nbStillDispersing--; + } + } + } + + pInd->setStatus(ind.status); + } +#if RS_RCPP + // write each individuals current movement step and status to paths file + if (trfr.usesMovtProc && sim.outPaths) { + if (nextseason >= sim.outStartPaths && nextseason % sim.outIntPaths == 0) { + pInd->outMovePath(nextseason); + } + } +#endif + + if (!trfr.usesMovtProc && sett.go2nbrLocn && (ind.status == 3 || ind.status == 6)) + { + // for kernel-based transfer only ... + // determine whether recruitment to a neighbouring cell is possible + + pCell = pInd->getCurrCell(); + newloc = pCell->getLocn(); + vector nbrlist; + for (int dx = -1; dx < 2; dx++) { + for (int dy = -1; dy < 2; dy++) { + if (dx != 0 || dy != 0) { //cell is not the current cell + nbrloc.x = newloc.x + dx; nbrloc.y = newloc.y + dy; + if (nbrloc.x >= 0 && nbrloc.x <= ppLand.maxX + && nbrloc.y >= 0 && nbrloc.y <= ppLand.maxY) { // within landscape + // add to list of potential neighbouring cells if suitable, etc. + pCell = pLandscape->findCell(nbrloc.x, nbrloc.y); + if (pCell != 0) { // not no-data area + pPatch = pCell->getPatch(); + if (pPatch != nullptr) { // not no-data area + patchnum = pPatch->getPatchNum(); + if (patchnum > 0 && pPatch != pInd->getNatalPatch()) + { // not the matrix or natal patch + if (pPatch->getK() > 0.0) + { // suitable + if (sett.findMate) { + if (matePresent(pSpecies, pCell, othersex)) nbrlist.push_back(pCell); + } + else + nbrlist.push_back(pCell); + } + } + } + } + } + } + } + } + int listsize = (int)nbrlist.size(); + if (listsize > 0) { // there is at least one suitable neighbouring cell + if (listsize == 1) { + pInd->moveto(nbrlist[0]); + } + else { // select at random from the list + int rrr = pRandom->IRandom(0, listsize - 1); + pInd->moveto(nbrlist[rrr]); + } + } + // else list empty - do nothing - individual retains its current location and status + } + + } // loop through inds + + } // loop through disperser map + + return nbStillDispersing; } //--------------------------------------------------------------------------- -// Remove emigrants from patch 0 (matrix) and transfer to sub-community +// Remove emigrants from the vectors map and transfer to sub-community // in which their destination co-ordinates fall -// This function is executed for the matrix patch only -void SubCommunity::completeDispersal(Landscape* pLandscape, bool connect) +void SubCommunity::completeDispersal(std::map>& inds_map, Landscape* pLandscape, bool connect) { - int popsize; - disperser settler; - Species* pSpecies; Population* pPop; Patch* pPrevPatch; Patch* pNewPatch; Cell* pPrevCell; SubCommunity* pSubComm; - int npops = (int)popns.size(); - for (int i = 0; i < npops; i++) { // all populations - pSpecies = popns[i]->getSpecies(); - popsize = popns[i]->getNInds(); - for (int j = 0; j < popsize; j++) { - bool settled; - settler = popns[i]->extractSettler(j); - settled = settler.yes; + for (auto & it : inds_map) { // all species + Species* const& pSpecies = it.first; + vector& inds = it.second; + for (Individual*& pInd : inds) { + indStats ind = pInd->getStats(); + bool settled = ind.status == 4 || ind.status == 5; if (settled) { - // settler - has already been removed from matrix population - // find new patch - pNewPatch = (Patch*)settler.pCell->getPatch(); + // find new patch + pNewPatch = pInd->getCurrCell()->getPatch(); // find population within the patch (if there is one) - pPop = (Population*)pNewPatch->getPopn((intptr)pSpecies); - if (pPop == 0) { // settler is the first in a previously uninhabited patch - // create a new population in the corresponding sub-community - pSubComm = (SubCommunity*)pNewPatch->getSubComm(); - pPop = pSubComm->newPopn(pLandscape, pSpecies, pNewPatch, 0); + { +#ifdef _OPENMP + const std::unique_lock lock = pNewPatch->lockPopns(); +#endif // _OPENMP + pPop = pNewPatch->getPopn(pSpecies); + if (pPop == 0) { // settler is the first in a previously uninhabited patch + // create a new population in the corresponding sub-community + pSubComm = pNewPatch->getSubComm(); + pPop = pSubComm->newPopn(pLandscape, pSpecies, pNewPatch, 0); + } } - pPop->recruit(settler.pInd); + pPop->recruit(pInd); if (connect) { // increment connectivity totals int newpatch = pNewPatch->getSeqNum(); - pPrevCell = settler.pInd->getLocn(0); // previous cell - intptr patch = pPrevCell->getPatch(); - if (patch != 0) { - pPrevPatch = (Patch*)patch; + pPrevCell = pInd->getPrevCell(); + pPrevPatch = pPrevCell->getPatch(); + if (pPrevPatch != nullptr) { int prevpatch = pPrevPatch->getSeqNum(); pLandscape->incrConnectMatrix(prevpatch, newpatch); } } + pInd = nullptr; } else { // for group dispersal only } } - // remove pointers in the matrix popn to settlers - popns[i]->clean(); + // remove settled individuals + inds.erase(std::remove(inds.begin(), inds.end(), (Individual *)nullptr), inds.end()); } - } //--------------------------------------------------------------------------- -void SubCommunity::survival(short part, short option0, short option1) +void SubCommunity::survival0(short option0, short option1) { int npops = (int)popns.size(); - if (npops < 1) return; - if (part == 0) { - float localK = pPatch->getK(); - for (int i = 0; i < npops; i++) { // all populations - popns[i]->survival0(localK, option0, option1); - } + std::vector localDemoScaling; + localDemoScaling = pPatch->getDemoScaling(); + float localK = pPatch->getK(); + for (int i = 0; i < npops; i++) { // all populations + popns[i]->survival0(localK, option0, option1, localDemoScaling); } +} + +void SubCommunity::survival1() +{ + int npops = (int)popns.size(); + for (int i = 0; i < npops; i++) { // all populations + popns[i]->survival1(); + } +} + +void SubCommunity::survival(short part, short option0, short option1 +) { + if (part == 0) { + return survival0(option0, option1); +} else { - for (int i = 0; i < npops; i++) { // all populations - popns[i]->survival1(); - } + return survival1(); } } @@ -436,17 +761,13 @@ void SubCommunity::ageIncrement(void) { // Find the population of a given species in a given patch Population* SubCommunity::findPop(Species* pSp, Patch* pPch) { -#if RSDEBUG - DEBUGLOG << "SubCommunity::findPop(): this=" << this - << endl; -#endif Population* pPop = 0; popStats pop; int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { // all populations - pop = popns[i]->getStats(); + pop = popns[i]->getStats(pPatch->getDemoScaling()); if (pop.pSpecies == pSp && pop.pPatch == pPch) { // population located pPop = popns[i]; break; @@ -470,7 +791,7 @@ void SubCommunity::updateOccupancy(int row) { popStats pop; int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { - pop = popns[i]->getStats(); + pop = popns[i]->getStats(pPatch->getDemoScaling()); if (pop.nInds > 0 && pop.breeding) { occupancy[row]++; i = npops; @@ -488,28 +809,34 @@ void SubCommunity::deleteOccupancy(void) { occupancy = 0; } +//--------------------------------------------------------------------------- +// Close population file +bool SubCommunity::outPopFinishLandscape() +{ + bool fileOK; + Population* pPop; + + // as all populations may have been deleted, set up a dummy one + // species is not necessary + pPop = new Population(); + fileOK = pPop->outPopFinishLandscape(); + delete pPop; + return fileOK; +} + //--------------------------------------------------------------------------- // Open population file and write header record -bool SubCommunity::outPopHeaders(Landscape* pLandscape, Species* pSpecies, int option) +bool SubCommunity::outPopStartLandscape(Landscape* pLandscape, Species* pSpecies) { bool fileOK; Population* pPop; landParams land = pLandscape->getLandParams(); - if (option == -999) { // close the file - // as all populations may have been deleted, set up a dummy one - // species is not necessary - pPop = new Population(); - fileOK = pPop->outPopHeaders(-999, land.patchModel); - delete pPop; - } - else { // open the file - // as no population has yet been created, set up a dummy one - // species is necessary, as columns depend on stage and sex structure - pPop = new Population(pSpecies, pPatch, 0, land.resol); - fileOK = pPop->outPopHeaders(land.landNum, land.patchModel); - delete pPop; - } + // as no population has yet been created, set up a dummy one + // species is necessary, as columns depend on stage and sex structure + pPop = new Population(pSpecies, pPatch, 0, land.resol); + fileOK = pPop->outPopStartLandscape(land.landNum, land.patchModel); + delete pPop; return fileOK; } @@ -553,72 +880,63 @@ void SubCommunity::outPop(Landscape* pLandscape, int rep, int yr, int gen) popns[i]->outPopulation(rep, yr, gen, eps, land.patchModel, writeEnv, gradK); } else { - if (popns[i]->totalPop() > 0) { + if (popns[i]->getNbInds() > 0) { popns[i]->outPopulation(rep, yr, gen, eps, land.patchModel, writeEnv, gradK); } } } } -// Write records to individuals file -void SubCommunity::outInds(Landscape* pLandscape, int rep, int yr, int gen, int landNr) { - landParams ppLand = pLandscape->getLandParams(); - if (landNr >= 0) { // open the file - popns[0]->outIndsHeaders(rep, landNr, ppLand.patchModel); - return; - } - if (landNr == -999) { // close the file - popns[0]->outIndsHeaders(rep, -999, ppLand.patchModel); +// Close individuals file +void SubCommunity::outIndsFinishReplicate() { + // as all populations have been deleted, set up a dummy one + Population* pPop = new Population(); + pPop->outIndsFinishReplicate(); + delete pPop; return; - } - // generate output for each population within the sub-community (patch) - int npops = (int)popns.size(); - for (int i = 0; i < npops; i++) { // all populations - popns[i]->outIndividual(pLandscape, rep, yr, gen, pPatch->getPatchNum()); - } +} + +// Open individuals file and write header record +void SubCommunity::outIndsStartReplicate(Landscape* pLandscape, int rep, int landNr) { + landParams ppLand = pLandscape->getLandParams(); + popns[0]->outIndsStartReplicate(rep, landNr, ppLand.patchModel); } // Write records to individuals file -void SubCommunity::outGenetics(int rep, int yr, int gen, int landNr) -{ - if (landNr >= 0) { // open the file - popns[0]->outGenetics(rep, yr, landNr); - return; - } - if (landNr == -999) { // close the file - popns[0]->outGenetics(rep, yr, landNr); - return; - } +void SubCommunity::outIndividuals(Landscape* pLandscape, int rep, int yr, int gen) { // generate output for each population within the sub-community (patch) int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { // all populations - popns[i]->outGenetics(rep, yr, landNr); + popns[i]->outIndividual(pLandscape, rep, yr, gen, pPatch->getPatchNum()); } } + // Population size of a specified stage -int SubCommunity::stagePop(int stage) { +int SubCommunity::getNbInds(int stage) const { int popsize = 0; int npops = (int)popns.size(); for (int i = 0; i < npops; i++) { // all populations - popsize += popns[i]->stagePop(stage); + popsize += popns[i]->getNbInds(stage); } return popsize; } +// Close traits file +bool SubCommunity::outTraitsFinishLandscape() +{ + if (outtraits.is_open()) outtraits.close(); + outtraits.clear(); + return true; +} + // Open traits file and write header record -bool SubCommunity::outTraitsHeaders(Landscape* pLandscape, Species* pSpecies, int landNr) +bool SubCommunity::outTraitsStartLandscape(Landscape* pLandscape, Species* pSpecies, int landNr) { landParams land = pLandscape->getLandParams(); - if (landNr == -999) { // close file - if (outtraits.is_open()) outtraits.close(); - outtraits.clear(); - return true; - } - string name; - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); simParams sim = paramsSim->getSim(); @@ -626,23 +944,23 @@ bool SubCommunity::outTraitsHeaders(Landscape* pLandscape, Species* pSpecies, in if (sim.batchMode) { if (land.patchModel) { name = DirOut - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) + "_Land" + Int2Str(landNr) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + to_string(landNr) + "_TraitsXpatch.txt"; } else { name = DirOut - + "Batch" + Int2Str(sim.batchNum) + "_" - + "Sim" + Int2Str(sim.simulation) + "_Land" + Int2Str(landNr) + + "Batch" + to_string(sim.batchNum) + "_" + + "Sim" + to_string(sim.simulation) + "_Land" + to_string(landNr) + "_TraitsXcell.txt"; } } else { if (land.patchModel) { - name = DirOut + "Sim" + Int2Str(sim.simulation) + "_TraitsXpatch.txt"; + name = DirOut + "Sim" + to_string(sim.simulation) + "_TraitsXpatch.txt"; } else { - name = DirOut + "Sim" + Int2Str(sim.simulation) + "_TraitsXcell.txt"; + name = DirOut + "Sim" + to_string(sim.simulation) + "_TraitsXcell.txt"; } } outtraits.open(name.c_str()); @@ -674,7 +992,7 @@ bool SubCommunity::outTraitsHeaders(Landscape* pLandscape, Species* pSpecies, in } } if (trfr.indVar) { - if (trfr.moveModel) { + if (trfr.usesMovtProc) { if (trfr.moveType == 1) { outtraits << "\tmeanDP\tstdDP\tmeanGB\tstdGB"; outtraits << "\tmeanAlphaDB\tstdAlphaDB\tmeanBetaDB\tstdBetaDB"; @@ -710,23 +1028,27 @@ bool SubCommunity::outTraitsHeaders(Landscape* pLandscape, Species* pSpecies, in outtraits << "\tmeanBetaS\tstdBetaS"; } } + if (pSpecies->getNbGenLoadTraits() > 0) { + if (pSpecies->getDemogrParams().repType > 0) { + outtraits << "\tF_meanGenFitness\tF_stdGenFitness\tM_meanGenFitness\tM_stdGenFitness"; + } + else { + outtraits << "\tmeanGenFitness\tstdGenFitness"; + } + } + outtraits << endl; return outtraits.is_open(); } // Write records to traits file and return aggregated sums -traitsums SubCommunity::outTraits(traitCanvas tcanv, - Landscape* pLandscape, int rep, int yr, int gen, bool commlevel) +traitsums SubCommunity::outTraits(Landscape* pLandscape, int rep, int yr, int gen, bool commlevel) { - int popsize, ngenes; landParams land = pLandscape->getLandParams(); simParams sim = paramsSim->getSim(); - bool writefile = false; - if (sim.outTraitsCells && yr % sim.outIntTraitCell == 0 && !commlevel) - writefile = true; - traitsums ts, poptraits; - for (int i = 0; i < NSEXES; i++) { + traitsums ts, indTraitsSums; + for (int i = 0; i < gMaxNbSexes; i++) { ts.ninds[i] = 0; ts.sumD0[i] = ts.ssqD0[i] = 0.0; ts.sumAlpha[i] = ts.ssqAlpha[i] = 0.0; ts.sumBeta[i] = ts.ssqBeta[i] = 0.0; @@ -739,25 +1061,65 @@ traitsums SubCommunity::outTraits(traitCanvas tcanv, ts.sumStepL[i] = ts.ssqStepL[i] = 0.0; ts.sumRho[i] = ts.ssqRho[i] = 0.0; ts.sumS0[i] = ts.ssqS0[i] = 0.0; ts.sumAlphaS[i] = ts.ssqAlphaS[i] = 0.0; ts.sumBetaS[i] = ts.ssqBetaS[i] = 0.0; + ts.sumGeneticFitness[i] = ts.ssqGeneticFitness[i] = 0.0; } // generate output for each population within the sub-community (patch) // provided that the patch is suitable (i.e. non-zero carrying capacity) int npops = (int)popns.size(); Species* pSpecies; - float localK; - for (int i = 0; i < npops; i++) { // all populations - localK = pPatch->getK(); - if (localK > 0.0 && popns[i]->getNInds() > 0) { - pSpecies = popns[i]->getSpecies(); - demogrParams dem = pSpecies->getDemogr(); - emigRules emig = pSpecies->getEmig(); - trfrRules trfr = pSpecies->getTrfr(); + for (int iPop = 0; iPop < npops; iPop++) { // all populations + + if (pPatch->getK() > 0.0 && popns[iPop]->getNbInds() > 0) { + pSpecies = popns[iPop]->getSpecies(); + demogrParams dem = pSpecies->getDemogrParams(); + emigRules emig = pSpecies->getEmigRules(); + transferRules trfr = pSpecies->getTransferRules(); settleType sett = pSpecies->getSettle(); - poptraits = popns[i]->getTraits(pSpecies); + indTraitsSums = popns[iPop]->getIndTraitsSums(pSpecies); + + // Sum over populations + for (int iSex = 0; iSex < gMaxNbSexes; iSex++) { + ts.ninds[iSex] += indTraitsSums.ninds[iSex]; + ts.sumD0[iSex] += indTraitsSums.sumD0[iSex]; + ts.ssqD0[iSex] += indTraitsSums.ssqD0[iSex]; + ts.sumAlpha[iSex] += indTraitsSums.sumAlpha[iSex]; + ts.ssqAlpha[iSex] += indTraitsSums.ssqAlpha[iSex]; + ts.sumBeta[iSex] += indTraitsSums.sumBeta[iSex]; + ts.ssqBeta[iSex] += indTraitsSums.ssqBeta[iSex]; + ts.sumDist1[iSex] += indTraitsSums.sumDist1[iSex]; + ts.ssqDist1[iSex] += indTraitsSums.ssqDist1[iSex]; + ts.sumDist2[iSex] += indTraitsSums.sumDist2[iSex]; + ts.ssqDist2[iSex] += indTraitsSums.ssqDist2[iSex]; + ts.sumProp1[iSex] += indTraitsSums.sumProp1[iSex]; + ts.ssqProp1[iSex] += indTraitsSums.ssqProp1[iSex]; + ts.sumDP[iSex] += indTraitsSums.sumDP[iSex]; + ts.ssqDP[iSex] += indTraitsSums.ssqDP[iSex]; + ts.sumGB[iSex] += indTraitsSums.sumGB[iSex]; + ts.ssqGB[iSex] += indTraitsSums.ssqGB[iSex]; + ts.sumAlphaDB[iSex] += indTraitsSums.sumAlphaDB[iSex]; + ts.ssqAlphaDB[iSex] += indTraitsSums.ssqAlphaDB[iSex]; + ts.sumBetaDB[iSex] += indTraitsSums.sumBetaDB[iSex]; + ts.ssqBetaDB[iSex] += indTraitsSums.ssqBetaDB[iSex]; + ts.sumStepL[iSex] += indTraitsSums.sumStepL[iSex]; + ts.ssqStepL[iSex] += indTraitsSums.ssqStepL[iSex]; + ts.sumRho[iSex] += indTraitsSums.sumRho[iSex]; + ts.ssqRho[iSex] += indTraitsSums.ssqRho[iSex]; + ts.sumS0[iSex] += indTraitsSums.sumS0[iSex]; + ts.ssqS0[iSex] += indTraitsSums.ssqS0[iSex]; + ts.sumAlphaS[iSex] += indTraitsSums.sumAlphaS[iSex]; + ts.ssqAlphaS[iSex] += indTraitsSums.ssqAlphaS[iSex]; + ts.sumBetaS[iSex] += indTraitsSums.sumBetaS[iSex]; + ts.ssqBetaS[iSex] += indTraitsSums.ssqBetaS[iSex]; + ts.sumGeneticFitness[iSex] += indTraitsSums.sumGeneticFitness[iSex]; + ts.ssqGeneticFitness[iSex] += indTraitsSums.ssqGeneticFitness[iSex]; + } + + // Produce trait-per-cell output + if (sim.outTraitsCells && yr % sim.outIntTraitCell == 0 + && !commlevel) { - if (writefile) { outtraits << rep << "\t" << yr << "\t" << gen; if (land.patchModel) { outtraits << "\t" << pPatch->getPatchNum(); @@ -766,46 +1128,33 @@ traitsums SubCommunity::outTraits(traitCanvas tcanv, locn loc = pPatch->getCellLocn(0); outtraits << "\t" << loc.x << "\t" << loc.y; } - } if (emig.indVar) { - if (emig.sexDep) { // must be a sexual species - ngenes = 2; - } - else { - if (dem.repType == 0) { // asexual reproduction - ngenes = 1; - } - else { // sexual reproduction - ngenes = 1; - } - } - double mnD0[2], mnAlpha[2], mnBeta[2], sdD0[2], sdAlpha[2], sdBeta[2]; - for (int g = 0; g < ngenes; g++) { - mnD0[g] = mnAlpha[g] = mnBeta[g] = sdD0[g] = sdAlpha[g] = sdBeta[g] = 0.0; - // individuals may have been counted by sex if there was - // sex dependency in another dispersal phase - if (ngenes == 2) popsize = poptraits.ninds[g]; - else popsize = poptraits.ninds[0] + poptraits.ninds[1]; + if (emig.sexDep) { + vector mnD0(2, 0.0), mnAlpha(2, 0.0), mnBeta(2, 0.0), sdD0(2, 0.0), sdAlpha(2, 0.0), sdBeta(2, 0.0); + for (int sex = 0; sex < gMaxNbSexes; sex++) { + + double popsize = static_cast(indTraitsSums.ninds[sex]); + if (popsize > 0) { - mnD0[g] = poptraits.sumD0[g] / (double)popsize; - mnAlpha[g] = poptraits.sumAlpha[g] / (double)popsize; - mnBeta[g] = poptraits.sumBeta[g] / (double)popsize; + + mnD0[sex] = indTraitsSums.sumD0[sex] / popsize; + mnAlpha[sex] = indTraitsSums.sumAlpha[sex] / popsize; + mnBeta[sex] = indTraitsSums.sumBeta[sex] / popsize; + if (popsize > 1) { - sdD0[g] = poptraits.ssqD0[g] / (double)popsize - mnD0[g] * mnD0[g]; - if (sdD0[g] > 0.0) sdD0[g] = sqrt(sdD0[g]); else sdD0[g] = 0.0; - sdAlpha[g] = poptraits.ssqAlpha[g] / (double)popsize - mnAlpha[g] * mnAlpha[g]; - if (sdAlpha[g] > 0.0) sdAlpha[g] = sqrt(sdAlpha[g]); else sdAlpha[g] = 0.0; - sdBeta[g] = poptraits.ssqBeta[g] / (double)popsize - mnBeta[g] * mnBeta[g]; - if (sdBeta[g] > 0.0) sdBeta[g] = sqrt(sdBeta[g]); else sdBeta[g] = 0.0; + sdD0[sex] = indTraitsSums.ssqD0[sex] / popsize - mnD0[sex] * mnD0[sex]; + if (sdD0[sex] > 0.0) sdD0[sex] = sqrt(sdD0[sex]); else sdD0[sex] = 0.0; + sdAlpha[sex] = indTraitsSums.ssqAlpha[sex] / popsize - mnAlpha[sex] * mnAlpha[sex]; + if (sdAlpha[sex] > 0.0) sdAlpha[sex] = sqrt(sdAlpha[sex]); else sdAlpha[sex] = 0.0; + sdBeta[sex] = indTraitsSums.ssqBeta[sex] / popsize - mnBeta[sex] * mnBeta[sex]; + if (sdBeta[sex] > 0.0) sdBeta[sex] = sqrt(sdBeta[sex]); else sdBeta[sex] = 0.0; } else { - sdD0[g] = sdAlpha[g] = sdBeta[g] = 0.0; + sdD0[sex] = sdAlpha[sex] = sdBeta[sex] = 0.0; } } } - if (writefile) { - if (emig.sexDep) { outtraits << "\t" << mnD0[0] << "\t" << sdD0[0]; outtraits << "\t" << mnD0[1] << "\t" << sdD0[1]; if (emig.densDep) { @@ -815,93 +1164,142 @@ traitsums SubCommunity::outTraits(traitCanvas tcanv, outtraits << "\t" << mnBeta[1] << "\t" << sdBeta[1]; } } - else { // sex-independent - outtraits << "\t" << mnD0[0] << "\t" << sdD0[0]; + else { // not sex-dependent + double mnD0 = 0, mnAlpha = 0, mnBeta = 0, popsize = 0; + double sdD0 = 0, sdAlpha = 0, sdBeta = 0; + for (int sex = 0; sex < gMaxNbSexes; sex++) { + mnD0 += indTraitsSums.sumD0[sex]; + mnAlpha += indTraitsSums.sumAlpha[sex]; + mnBeta += indTraitsSums.sumBeta[sex]; + popsize += indTraitsSums.ninds[sex]; + sdD0 += indTraitsSums.ssqD0[sex]; + sdAlpha += indTraitsSums.ssqAlpha[sex]; + sdBeta += indTraitsSums.ssqBeta[sex]; + } + mnD0 /= popsize; + mnAlpha /= popsize; + mnBeta /= popsize; + if (popsize > 1) { + sdD0 = sdD0 / popsize - mnD0 * mnD0; + sdAlpha = sdAlpha / popsize - mnAlpha * mnAlpha; + sdBeta = sdBeta / popsize - mnBeta * mnBeta; + sdD0 = sdD0 == 0.0 ? 0.0 : sqrt(sdD0); + sdAlpha = sdAlpha == 0.0 ? 0.0 : sqrt(sdAlpha); + sdBeta = sdBeta == 0.0 ? 0.0 : sqrt(sdBeta); + } + else { + sdD0 = 0.0; + sdAlpha = 0.0; + sdBeta = 0.0; + } + + outtraits << "\t" << mnD0 << "\t" << sdD0; if (emig.densDep) { - outtraits << "\t" << mnAlpha[0] << "\t" << sdAlpha[0]; - outtraits << "\t" << mnBeta[0] << "\t" << sdBeta[0]; + outtraits << "\t" << mnAlpha << "\t" << sdAlpha; + outtraits << "\t" << mnBeta << "\t" << sdBeta; } } } - } if (trfr.indVar) { - if (trfr.moveModel) { - // CURRENTLY INDIVIDUAL VARIATION CANNOT BE SEX-DEPENDENT - ngenes = 1; + + if (trfr.usesMovtProc) { // not sex-dependent + if (trfr.moveType == 1) { // SMS + double mnDP = 0.0, mnGB = 0.0, mnAlphaDB = 0.0, mnBetaDB = 0.0; + double sdDP = 0.0, sdGB = 0.0, sdAlphaDB = 0.0, sdBetaDB = 0.0; + double popsize = 0.0; + for (int sex = 0; sex < gMaxNbSexes; sex++) { + mnDP += indTraitsSums.sumDP[sex]; + mnGB += indTraitsSums.sumGB[sex]; + mnAlphaDB += indTraitsSums.sumAlphaDB[sex]; + mnBetaDB+= indTraitsSums.sumBetaDB[sex]; + popsize += indTraitsSums.ninds[sex]; + sdDP += indTraitsSums.ssqDP[sex]; + sdGB += indTraitsSums.ssqGB[sex]; + sdAlphaDB += indTraitsSums.ssqAlphaDB[sex]; + sdBetaDB += indTraitsSums.ssqBetaDB[sex]; } + mnDP /= popsize; + mnGB /= popsize; + mnAlphaDB /= popsize; + mnBetaDB /= popsize; + if (popsize > 1) { + sdDP = sdDP / popsize - mnDP * mnDP; + sdGB = sdGB / popsize - mnGB * mnGB; + sdAlphaDB = sdAlphaDB / popsize - mnAlphaDB * mnAlphaDB; + sdBetaDB = sdBetaDB / popsize - mnBetaDB * mnBetaDB; + sdDP = sdDP == 0.0 ? 0.0 : sqrt(sdDP); + sdGB = sdGB == 0.0 ? 0.0 : sqrt(sdGB); + sdAlphaDB = sdAlphaDB == 0.0 ? 0.0 : sqrt(sdAlphaDB); + sdBetaDB = sdBetaDB == 0.0 ? 0.0 : sqrt(sdBetaDB); + } else { - if (trfr.sexDep) { // must be a sexual species - ngenes = 2; + sdDP = 0.0; + sdGB = 0.0; + sdAlphaDB = 0.0; + sdBetaDB = 0.0; } + outtraits << "\t" << mnDP << "\t" << sdDP; + outtraits << "\t" << mnGB << "\t" << sdGB; + outtraits << "\t" << mnAlphaDB << "\t" << sdAlphaDB; + outtraits << "\t" << mnBetaDB << "\t" << sdBetaDB; + } + if (trfr.moveType == 2) { // CRW + double mnStepL = 0.0, mnRho = 0.0, sdStepL = 0.0, sdRho = 0.0; + double popsize = 0.0; + for (int sex = 0; sex < gMaxNbSexes; sex++) { + mnStepL += indTraitsSums.sumStepL[sex]; + mnRho += indTraitsSums.sumRho[sex]; + popsize += indTraitsSums.ninds[sex]; + sdStepL += indTraitsSums.ssqStepL[sex]; + sdRho += indTraitsSums.ssqRho[sex]; + } + mnStepL /= popsize; + mnRho /= popsize; + if (popsize > 1) { + sdStepL = sdStepL / popsize - mnStepL * mnStepL; + sdRho = sdRho / popsize - mnRho * mnRho; + sdStepL = sdStepL == 0.0 ? 0.0 : sqrt(sdStepL); + sdRho = sdRho == 0.0 ? 0.0 : sqrt(sdRho); + } else { - ngenes = 1; + sdStepL = 0.0; + sdRho = 0.0; } + outtraits << "\t" << mnStepL << "\t" << sdStepL; + outtraits << "\t" << mnRho << "\t" << sdRho; } - double mnDist1[2], mnDist2[2], mnProp1[2], mnStepL[2], mnRho[2]; - double sdDist1[2], sdDist2[2], sdProp1[2], sdStepL[2], sdRho[2]; - double mnDP[2], mnGB[2], mnAlphaDB[2], mnBetaDB[2]; - double sdDP[2], sdGB[2], sdAlphaDB[2], sdBetaDB[2]; - for (int g = 0; g < ngenes; g++) { - mnDist1[g] = mnDist2[g] = mnProp1[g] = mnStepL[g] = mnRho[g] = 0.0; - sdDist1[g] = sdDist2[g] = sdProp1[g] = sdStepL[g] = sdRho[g] = 0.0; - mnDP[g] = mnGB[g] = mnAlphaDB[g] = mnBetaDB[g] = 0.0; - sdDP[g] = sdGB[g] = sdAlphaDB[g] = sdBetaDB[g] = 0.0; + } + else { // kernels + if (trfr.sexDep) { + + vector mnDist1(2, 0.0), mnDist2(2, 0.0), mnProp1(2, 0.0), mnStepL(2, 0.0), mnRho(2, 0.0); + vector sdDist1(2, 0.0), sdDist2(2, 0.0), sdProp1(2, 0.0), sdStepL(2, 0.0), sdRho(2, 0.0); + + for (int sex = 0; sex < gMaxNbSexes; sex++) { + // individuals may have been counted by sex if there was // sex dependency in another dispersal phase - if (ngenes == 2) popsize = poptraits.ninds[g]; - else popsize = poptraits.ninds[0] + poptraits.ninds[1]; + double popsize = static_cast(indTraitsSums.ninds[sex]); + if (popsize > 0) { - mnDist1[g] = poptraits.sumDist1[g] / (double)popsize; - mnDist2[g] = poptraits.sumDist2[g] / (double)popsize; - mnProp1[g] = poptraits.sumProp1[g] / (double)popsize; - mnStepL[g] = poptraits.sumStepL[g] / (double)popsize; - mnRho[g] = poptraits.sumRho[g] / (double)popsize; - mnDP[g] = poptraits.sumDP[g] / (double)popsize; - mnGB[g] = poptraits.sumGB[g] / (double)popsize; - mnAlphaDB[g] = poptraits.sumAlphaDB[g] / (double)popsize; - mnBetaDB[g] = poptraits.sumBetaDB[g] / (double)popsize; + mnDist1[sex] = indTraitsSums.sumDist1[sex] / popsize; + mnDist2[sex] = indTraitsSums.sumDist2[sex] / popsize; + mnProp1[sex] = indTraitsSums.sumProp1[sex] / popsize; if (popsize > 1) { - sdDist1[g] = poptraits.ssqDist1[g] / (double)popsize - mnDist1[g] * mnDist1[g]; - if (sdDist1[g] > 0.0) sdDist1[g] = sqrt(sdDist1[g]); else sdDist1[g] = 0.0; - sdDist2[g] = poptraits.ssqDist2[g] / (double)popsize - mnDist2[g] * mnDist2[g]; - if (sdDist2[g] > 0.0) sdDist2[g] = sqrt(sdDist2[g]); else sdDist2[g] = 0.0; - sdProp1[g] = poptraits.ssqProp1[g] / (double)popsize - mnProp1[g] * mnProp1[g]; - if (sdProp1[g] > 0.0) sdProp1[g] = sqrt(sdProp1[g]); else sdProp1[g] = 0.0; - sdStepL[g] = poptraits.ssqStepL[g] / (double)popsize - mnStepL[g] * mnStepL[g]; - if (sdStepL[g] > 0.0) sdStepL[g] = sqrt(sdStepL[g]); else sdStepL[g] = 0.0; - sdRho[g] = poptraits.ssqRho[g] / (double)popsize - mnRho[g] * mnRho[g]; - if (sdRho[g] > 0.0) sdRho[g] = sqrt(sdRho[g]); else sdRho[g] = 0.0; - sdDP[g] = poptraits.ssqDP[g] / (double)popsize - mnDP[g] * mnDP[g]; - if (sdDP[g] > 0.0) sdDP[g] = sqrt(sdDP[g]); else sdDP[g] = 0.0; - sdGB[g] = poptraits.ssqGB[g] / (double)popsize - mnGB[g] * mnGB[g]; - if (sdGB[g] > 0.0) sdGB[g] = sqrt(sdGB[g]); else sdGB[g] = 0.0; - sdAlphaDB[g] = poptraits.ssqAlphaDB[g] / (double)popsize - mnAlphaDB[g] * mnAlphaDB[g]; - if (sdAlphaDB[g] > 0.0) sdAlphaDB[g] = sqrt(sdAlphaDB[g]); else sdAlphaDB[g] = 0.0; - sdBetaDB[g] = poptraits.ssqBetaDB[g] / (double)popsize - mnBetaDB[g] * mnBetaDB[g]; - if (sdBetaDB[g] > 0.0) sdBetaDB[g] = sqrt(sdBetaDB[g]); else sdBetaDB[g] = 0.0; + sdDist1[sex] = indTraitsSums.ssqDist1[sex] / popsize - mnDist1[sex] * mnDist1[sex]; + if (sdDist1[sex] > 0.0) sdDist1[sex] = sqrt(sdDist1[sex]); else sdDist1[sex] = 0.0; + sdDist2[sex] = indTraitsSums.ssqDist2[sex] / popsize - mnDist2[sex] * mnDist2[sex]; + if (sdDist2[sex] > 0.0) sdDist2[sex] = sqrt(sdDist2[sex]); else sdDist2[sex] = 0.0; + sdProp1[sex] = indTraitsSums.ssqProp1[sex] / popsize - mnProp1[sex] * mnProp1[sex]; + if (sdProp1[sex] > 0.0) sdProp1[sex] = sqrt(sdProp1[sex]); else sdProp1[sex] = 0.0; + sdStepL[sex] = indTraitsSums.ssqStepL[sex] / popsize - mnStepL[sex] * mnStepL[sex]; } } } - if (writefile) { - if (trfr.moveModel) { - if (trfr.moveType == 1) { - outtraits << "\t" << mnDP[0] << "\t" << sdDP[0]; - outtraits << "\t" << mnGB[0] << "\t" << sdGB[0]; - outtraits << "\t" << mnAlphaDB[0] << "\t" << sdAlphaDB[0]; - outtraits << "\t" << mnBetaDB[0] << "\t" << sdBetaDB[0]; - } - if (trfr.moveType == 2) { - outtraits << "\t" << mnStepL[0] << "\t" << sdStepL[0]; - outtraits << "\t" << mnRho[0] << "\t" << sdRho[0]; - } - } - else { - if (trfr.sexDep) { outtraits << "\t" << mnDist1[0] << "\t" << sdDist1[0]; outtraits << "\t" << mnDist1[1] << "\t" << sdDist1[1]; - if (trfr.twinKern) - { + if (trfr.twinKern) { outtraits << "\t" << mnDist2[0] << "\t" << sdDist2[0]; outtraits << "\t" << mnDist2[1] << "\t" << sdDist2[1]; outtraits << "\t" << mnProp1[0] << "\t" << sdProp1[0]; @@ -909,56 +1307,74 @@ traitsums SubCommunity::outTraits(traitCanvas tcanv, } } else { // sex-independent - outtraits << "\t" << mnDist1[0] << "\t" << sdDist1[0]; - if (trfr.twinKern) - { - outtraits << "\t" << mnDist2[0] << "\t" << sdDist2[0]; - outtraits << "\t" << mnProp1[0] << "\t" << sdProp1[0]; + double mnDist1 = 0.0, mnDist2 = 0.0, mnProp1 = 0.0, popsize = 0.0; + double sdDist1 = 0.0, sdDist2 = 0.0, sdProp1 = 0.0; + for (int sex = 0; sex < gMaxNbSexes; sex++) { + mnDist1 += indTraitsSums.sumDist1[sex]; + mnDist2 += indTraitsSums.sumDist2[sex]; + mnProp1 += indTraitsSums.sumProp1[sex]; + popsize += indTraitsSums.ninds[sex]; + sdDist1 += indTraitsSums.ssqDist1[sex]; + sdDist2 += indTraitsSums.ssqDist2[sex]; + sdProp1 += indTraitsSums.ssqProp1[sex]; } + mnDist1 /= popsize; + mnDist2 /= popsize; + mnProp1 /= popsize; + if (popsize > 1) { + sdDist1 = sdDist1 / popsize - mnDist1 * mnDist1; + sdDist2 = sdDist2 / popsize - mnDist2 * mnDist2; + sdProp1 = sdProp1 / popsize - mnProp1 * mnProp1; + sdDist1 = sdDist1 == 0.0 ? 0.0 : sqrt(sdDist1); + sdDist2 = sdDist2 == 0.0 ? 0.0 : sqrt(sdDist2); + sdProp1 = sdProp1 == 0.0 ? 0.0 : sqrt(sdProp1); } + else { + sdDist1 = 0.0; + sdDist2 = 0.0; + sdProp1 = 0.0; } + outtraits << "\t" << mnDist1 << "\t" << sdDist1; + if (trfr.twinKern) { + outtraits << "\t" << mnDist2 << "\t" << sdDist2; + outtraits << "\t" << mnProp1 << "\t" << sdProp1; } } - - if (sett.indVar) { - if (sett.sexDep) { // must be a sexual species - ngenes = 2; - } - else { - if (dem.repType == 0) { // asexual reproduction - ngenes = 1; - } - else { // sexual reproduction - ngenes = 1; } } - // CURRENTLY INDIVIDUAL VARIATION CANNOT BE SEX-DEPENDENT - double mnS0[2], mnAlpha[2], mnBeta[2], sdS0[2], sdAlpha[2], sdBeta[2]; - for (int g = 0; g < ngenes; g++) { - mnS0[g] = mnAlpha[g] = mnBeta[g] = sdS0[g] = sdAlpha[g] = sdBeta[g] = 0.0; + + if (sett.indVar) { + + if (sett.sexDep) { + + vector mnS0(2, 0.0), mnAlpha(2, 0.0), mnBeta(2, 0.0), sdS0(2, 0.0), sdAlpha(2, 0.0), sdBeta(2, 0.0); + + for (int sex = 0; sex < gMaxNbSexes; sex++) { + // individuals may have been counted by sex if there was // sex dependency in another dispersal phase - if (ngenes == 2) popsize = poptraits.ninds[g]; - else popsize = poptraits.ninds[0] + poptraits.ninds[1]; + double popsize = static_cast(indTraitsSums.ninds[sex]); + if (popsize > 0) { - mnS0[g] = poptraits.sumS0[g] / (double)popsize; - mnAlpha[g] = poptraits.sumAlphaS[g] / (double)popsize; - mnBeta[g] = poptraits.sumBetaS[g] / (double)popsize; + + mnS0[sex] = indTraitsSums.sumS0[sex] / popsize; + mnAlpha[sex] = indTraitsSums.sumAlphaS[sex] / popsize; + mnBeta[sex] = indTraitsSums.sumBetaS[sex] / popsize; + if (popsize > 1) { - sdS0[g] = poptraits.ssqS0[g] / (double)popsize - mnS0[g] * mnS0[g]; - if (sdS0[g] > 0.0) sdS0[g] = sqrt(sdS0[g]); else sdS0[g] = 0.0; - sdAlpha[g] = poptraits.ssqAlphaS[g] / (double)popsize - mnAlpha[g] * mnAlpha[g]; - if (sdAlpha[g] > 0.0) sdAlpha[g] = sqrt(sdAlpha[g]); else sdAlpha[g] = 0.0; - sdBeta[g] = poptraits.ssqBetaS[g] / (double)popsize - mnBeta[g] * mnBeta[g]; - if (sdBeta[g] > 0.0) sdBeta[g] = sqrt(sdBeta[g]); else sdBeta[g] = 0.0; + sdS0[sex] = indTraitsSums.ssqS0[sex] / popsize - mnS0[sex] * mnS0[sex]; + if (sdS0[sex] > 0.0) sdS0[sex] = sqrt(sdS0[sex]); else sdS0[sex] = 0.0; + sdAlpha[sex] = indTraitsSums.ssqAlphaS[sex] / popsize - mnAlpha[sex] * mnAlpha[sex]; + if (sdAlpha[sex] > 0.0) sdAlpha[sex] = sqrt(sdAlpha[sex]); else sdAlpha[sex] = 0.0; + sdBeta[sex] = indTraitsSums.ssqBetaS[sex] / popsize - mnBeta[sex] * mnBeta[sex]; + if (sdBeta[sex] > 0.0) sdBeta[sex] = sqrt(sdBeta[sex]); else sdBeta[sex] = 0.0; } else { - sdS0[g] = sdAlpha[g] = sdBeta[g] = 0.0; + sdS0[sex] = sdAlpha[sex] = sdBeta[sex] = 0.0; } } } - if (writefile) { - if (sett.sexDep) { + outtraits << "\t" << mnS0[0] << "\t" << sdS0[0]; outtraits << "\t" << mnS0[1] << "\t" << sdS0[1]; outtraits << "\t" << mnAlpha[0] << "\t" << sdAlpha[0]; @@ -967,35 +1383,86 @@ traitsums SubCommunity::outTraits(traitCanvas tcanv, outtraits << "\t" << mnBeta[1] << "\t" << sdBeta[1]; } else { // sex-independent - outtraits << "\t" << mnS0[0] << "\t" << sdS0[0]; - outtraits << "\t" << mnAlpha[0] << "\t" << sdAlpha[0]; - outtraits << "\t" << mnBeta[0] << "\t" << sdBeta[0]; + double mnS0 = 0, mnAlpha = 0, mnBeta = 0, popsize = 0; + double sdS0 = 0, sdAlpha = 0, sdBeta = 0; + for (int sex = 0; sex < gMaxNbSexes; sex++) { + mnS0 += indTraitsSums.sumS0[sex]; + mnAlpha += indTraitsSums.sumAlphaS[sex]; + mnBeta += indTraitsSums.sumBetaS[sex]; + popsize += indTraitsSums.ninds[sex]; + sdS0 += indTraitsSums.ssqS0[sex]; + sdAlpha += indTraitsSums.ssqAlphaS[sex]; + sdBeta += indTraitsSums.ssqBetaS[sex]; } + mnS0 /= popsize; + mnAlpha /= popsize; + mnBeta /= popsize; + if (popsize > 1) { + sdS0 = sdS0 / popsize - mnS0 * mnS0; + sdAlpha = sdAlpha / popsize - mnAlpha * mnAlpha; + sdBeta = sdBeta / popsize - mnBeta * mnBeta; + sdS0 = sdS0 == 0.0 ? 0.0 : sqrt(sdS0); + sdAlpha = sdAlpha == 0.0 ? 0.0 : sqrt(sdAlpha); + sdBeta = sdBeta == 0.0 ? 0.0 : sqrt(sdBeta); } + else { + sdS0 = 0.0; + sdAlpha = 0.0; + sdBeta = 0.0; } + outtraits << "\t" << mnS0 << "\t" << sdS0; + outtraits << "\t" << mnAlpha << "\t" << sdAlpha; + outtraits << "\t" << mnBeta << "\t" << sdBeta; + } + } - if (writefile) outtraits << endl; - - for (int s = 0; s < NSEXES; s++) { - ts.ninds[s] += poptraits.ninds[s]; - ts.sumD0[s] += poptraits.sumD0[s]; ts.ssqD0[s] += poptraits.ssqD0[s]; - ts.sumAlpha[s] += poptraits.sumAlpha[s]; ts.ssqAlpha[s] += poptraits.ssqAlpha[s]; - ts.sumBeta[s] += poptraits.sumBeta[s]; ts.ssqBeta[s] += poptraits.ssqBeta[s]; - ts.sumDist1[s] += poptraits.sumDist1[s]; ts.ssqDist1[s] += poptraits.ssqDist1[s]; - ts.sumDist2[s] += poptraits.sumDist2[s]; ts.ssqDist2[s] += poptraits.ssqDist2[s]; - ts.sumProp1[s] += poptraits.sumProp1[s]; ts.ssqProp1[s] += poptraits.ssqProp1[s]; - ts.sumDP[s] += poptraits.sumDP[s]; ts.ssqDP[s] += poptraits.ssqDP[s]; - ts.sumGB[s] += poptraits.sumGB[s]; ts.ssqGB[s] += poptraits.ssqGB[s]; - ts.sumAlphaDB[s] += poptraits.sumAlphaDB[s]; ts.ssqAlphaDB[s] += poptraits.ssqAlphaDB[s]; - ts.sumBetaDB[s] += poptraits.sumBetaDB[s]; ts.ssqBetaDB[s] += poptraits.ssqBetaDB[s]; - ts.sumStepL[s] += poptraits.sumStepL[s]; ts.ssqStepL[s] += poptraits.ssqStepL[s]; - ts.sumRho[s] += poptraits.sumRho[s]; ts.ssqRho[s] += poptraits.ssqRho[s]; - ts.sumS0[s] += poptraits.sumS0[s]; ts.ssqS0[s] += poptraits.ssqS0[s]; - ts.sumAlphaS[s] += poptraits.sumAlphaS[s]; ts.ssqAlphaS[s] += poptraits.ssqAlphaS[s]; - ts.sumBetaS[s] += poptraits.sumBetaS[s]; ts.ssqBetaS[s] += poptraits.ssqBetaS[s]; + // Genetic load + if (pSpecies->getNbGenLoadTraits() > 0) { + + if (pSpecies->getDemogrParams().repType > 0) { // sexual model + vector mnGenFitness(2, 0.0), sdGenFitness(2, 0.0); + + for (int sex = 0; sex < gMaxNbSexes; sex++) { + double popsize = static_cast(indTraitsSums.ninds[sex]); + mnGenFitness[sex] = indTraitsSums.sumGeneticFitness[sex] + / popsize; + if (popsize > 1) { + sdGenFitness[sex] = indTraitsSums.ssqGeneticFitness[sex] + / popsize - mnGenFitness[sex] + * mnGenFitness[sex]; + if (sdGenFitness[sex] > 0.0) + sdGenFitness[sex] = sqrt(sdGenFitness[sex]); + else sdGenFitness[sex] = 0.0; } + else { + sdGenFitness[sex] = 0.0; } } + outtraits << "\t" << mnGenFitness[0] << "\t" << sdGenFitness[0]; + outtraits << "\t" << mnGenFitness[1] << "\t" << sdGenFitness[1]; + } + else { // asexual + + double mnGenFitness = 0.0, popsize = 0.0, sdGenFitness= 0.0; + for (int sex = 0; sex < gMaxNbSexes; sex++) { + mnGenFitness += indTraitsSums.sumGeneticFitness[sex]; + popsize += indTraitsSums.ninds[sex]; + sdGenFitness += indTraitsSums.ssqGeneticFitness[sex]; + } + mnGenFitness /= popsize; + if (popsize > 1) { + sdGenFitness = sdGenFitness / popsize - mnGenFitness * mnGenFitness; + sdGenFitness = sdGenFitness == 0.0 ? 0.0 : sqrt(sdGenFitness); + } + else sdGenFitness = 0.0; + outtraits << "\t" << mnGenFitness << "\t" << sdGenFitness; + } + } + outtraits << endl; + } // end trait-per-cell output + + } + } // end population loop return ts; } diff --git a/SubCommunity.h b/SubCommunity.h index 3eef73a..bfc421d 100644 --- a/SubCommunity.h +++ b/SubCommunity.h @@ -1,6 +1,6 @@ /*---------------------------------------------------------------------------- * - * Copyright (C) 2020 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Damaris Zurell + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell * * This file is part of RangeShifter. * @@ -39,7 +39,7 @@ Methods in Ecology and Evolution, 5, 388-396. doi: 10.1111/2041-210X.12162 Authors: Greta Bocedi & Steve Palmer, University of Aberdeen -Last updated: 26 October 2021 by Steve Palmer + Last updated: 25 June 2021 by Greta Bocedi ------------------------------------------------------------------------------*/ @@ -48,6 +48,7 @@ Last updated: 26 October 2021 by Steve Palmer #include #include +#include using namespace std; #include "Parameters.h" @@ -56,16 +57,12 @@ using namespace std; //--------------------------------------------------------------------------- -struct traitCanvas { // canvases for drawing variable traits - int *pcanvas[NTRAITS]; // dummy variables for batch version -}; - class SubCommunity { public: SubCommunity(Patch*,int); ~SubCommunity(void); - intptr getNum(void); + int getNum(void); Patch* getPatch(void); locn getLocn(void); @@ -94,35 +91,66 @@ class SubCommunity { bool // TRUE for a patch-based model, FALSE for a cell-based model ); void emigration(void); - // Remove emigrants from their natal patch and add to patch 0 (matrix) - void initiateDispersal( - SubCommunity* // pointer to matrix SubCommunity + + // Remove emigrants from the matrix subcommunity and add to a map of vectors + void disperseMatrix( + std::map>& ); -// Add an individual into the local population of its species in the patch + // Remove emigrants from their natal patch and add to a map of vectors + void recruitDispersers( + std::map>& + ); + // Add an individual into the local population of its species in the patch void recruit( Individual*, // pointer to Individual Species* // pointer to Species ); -#if RS_RCPP - int transfer( // Transfer through matrix - run for matrix SubCommunity only - Landscape*, // pointer to Landscape - short, // landscape change index - short // season / year + // Add individuals into the local population of their species in the patch + void recruitMany( + std::vector&, // vector of pointers to Individuals + Species* // pointer to Species ); -#else - int transfer( // Transfer through matrix - run for matrix SubCommunity only + + // Determine whether there is a potential mate present in a patch which a potential + // settler has reached + static bool matePresent( + Species* pSpecies, + Cell*, // pointer to the Cell which the potential settler has reached + short // sex of the required mate (0 = female, 1 = male) + ); + + static int resolveTransfer( // Executed for a given vector of individuals + std::map>& dispersingInds, Landscape*, // pointer to Landscape short // landscape change index ); + +#if RS_RCPP + static int resolveSettlement( // Executed for a given vector of individuals + std::map>& dispersingInds, + Landscape* pLandscape, + short // year + ); +#else + static int resolveSettlement( // Executed for a given vector of individuals + std::map>& dispersingInds, + Landscape* pLandscape + ); #endif // RS_RCPP - // Remove emigrants from patch 0 (matrix) and transfer to SubCommunity in which - // their destination co-ordinates fall (executed for the matrix patch only) - void completeDispersal( + + // Remove emigrants from the vectors map and transfer to SubCommunity in which + // their destination co-ordinates fall + static void completeDispersal( + std::map>&, // per-species map of vectors of individuals Landscape*, // pointer to Landscape bool // TRUE to increment connectivity totals ); + + void survival0(short option0, short option1); + void survival1(); + void survival( - short, // part: 0 = determine survival & development, + short, // part: 0 = determine survival & development, // 1 = apply survival changes to the population short, // option0: 0 = stage 0 (juveniles) only ) // 1 = all stages ) used by part 0 only @@ -130,9 +158,12 @@ class SubCommunity { short // option1: 0 - development only (when survival is annual) // 1 - development and survival ); + void ageIncrement(void); + // Find the population of a given species in a given patch Population* findPop(Species*,Patch*); + void createOccupancy( int // no. of rows = (no. of years / interval) + 1 ); @@ -144,10 +175,10 @@ class SubCommunity { ); void deleteOccupancy(void); - bool outPopHeaders( // Open population file and write header record + bool outPopFinishLandscape(); // Close population file + bool outPopStartLandscape( // Open population file and write header record Landscape*, // pointer to Landscape - Species*, // pointer to Species - int // option: -999 to close the file + Species* // pointer to Species ); void outPop( // Write records to population file Landscape*, // pointer to Landscape @@ -156,41 +187,44 @@ class SubCommunity { int // generation ); - void outInds( // Write records to individuals file + void outIndsFinishReplicate(); // Close individuals file + void outIndsStartReplicate( // Open individuals file and write header record Landscape*, // pointer to Landscape int, // replicate - int, // year - int, // generation - int // Landscape number (>= 0 to open the file, -999 to close the file - // -1 to write data records) + int // Landscape number ); - void outGenetics( // Write records to genetics file + void outIndividuals( // Write records to individuals file + Landscape*, // pointer to Landscape int, // replicate int, // year - int, // generation - int // Landscape number (>= 0 to open the file, -999 to close the file - // -1 to write data records) + int // generation ); bool outTraitsHeaders( // Open traits file and write header record Landscape*, // pointer to Landscape Species*, // pointer to Species - int // Landscape number (-999 to close the file) + int // Landscape number ); + + + // Close traits file + bool outTraitsFinishLandscape(); + + // Open traits file and write header record + bool outTraitsStartLandscape(Landscape* pLandscape, Species* pSpecies, int landNr); + traitsums outTraits( // Write records to traits file and return aggregated sums - traitCanvas, // pointers to canvases for drawing variable traits - // in the batch version, these are replaced by integers set to zero Landscape*, // pointer to Landscape int, // replicate int, // year int, // generation bool // true if called to summarise data at community level ); - int stagePop( // Population size of a specified stage + int getNbInds( // Population size of a specified stage int // stage - ); + ) const; private: - intptr subCommNum; // SubCommunity number + int subCommNum; // SubCommunity number // 0 is reserved for the SubCommunity in the inter-patch matrix Patch *pPatch; int *occupancy; // pointer to occupancy array diff --git a/TraitFactory.h b/TraitFactory.h new file mode 100644 index 0000000..2735d49 --- /dev/null +++ b/TraitFactory.h @@ -0,0 +1,55 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + * + * File Created by Roslyn Henry March 2023. Code adapted from NEMO (https://nemo2.sourceforge.io/) + --------------------------------------------------------------------------*/ +#ifndef TRAITFACTORYH +#define TRAITFACTORYH + +#include + +#include "SpeciesTrait.h" +#include "NeutralTrait.h" +#include "DispersalTrait.h" +#include "GeneticFitnessTrait.h" + +// Create handled pointers to a new trait +class TraitFactory +{ +public: + TraitFactory() {}; + + unique_ptr Create(const TraitType traitType, SpeciesTrait* pSpTrait) + { + if (traitType == NEUTRAL) { + return make_unique(pSpTrait); + } + else if (traitType == GENETIC_LOAD1 + || traitType == GENETIC_LOAD2 + || traitType == GENETIC_LOAD3 + || traitType == GENETIC_LOAD4 + || traitType == GENETIC_LOAD5) { + return make_unique(pSpTrait); + } + else { + return make_unique(pSpTrait); + } + } +}; +#endif diff --git a/Utils.cpp b/Utils.cpp index 9d36b92..e5f6f9c 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -1,26 +1,28 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + --------------------------------------------------------------------------*/ #include "Utils.h" -const string Int2Str(const int x) -{ - ostringstream o; - if (!(o << x)) return "ERROR"; - return o.str(); -} -const string Float2Str(const float x) { - ostringstream o; - if (!(o << x)) return "ERROR"; - return o.str(); -} -const string Double2Str(const double x) { - ostringstream o; - if (!(o << x)) return "ERROR"; - return o.str(); -} - // Evaluate a lambda and assert we get the correct error void assert_error(const string& exptd_err_msg, void (*lambda)(void)) { string err_msg{ "No error.\n" }; try { lambda(); } // evaluate catch (exception& e) { err_msg = e.what(); } assert(err_msg == exptd_err_msg); -} \ No newline at end of file +} diff --git a/Utils.h b/Utils.h index 34854a7..2f93e82 100644 --- a/Utils.h +++ b/Utils.h @@ -1,3 +1,22 @@ +/*---------------------------------------------------------------------------- + * + * Copyright (C) 2026 Greta Bocedi, Stephen C.F. Palmer, Justin M.J. Travis, Anne-Kathleen Malchow, Roslyn Henry, Théo Pannetier, Jette Wolff, Damaris Zurell + * + * This file is part of RangeShifter. + * + * RangeShifter 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. + * + * RangeShifter 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 RangeShifter. If not, see . + --------------------------------------------------------------------------*/ #ifndef UtilsH #define UtilsH @@ -8,10 +27,6 @@ #include using namespace std; -const string Int2Str(const int x); -const string Float2Str(const float x); -const string Double2Str(const double x); - // Evaluate a lambda and assert we get the correct error void assert_error(const string& exptd_err_msg, void (*x)(void)); diff --git a/unit_tests/testIndividual.cpp b/unit_tests/testIndividual.cpp new file mode 100644 index 0000000..2955609 --- /dev/null +++ b/unit_tests/testIndividual.cpp @@ -0,0 +1,1406 @@ +#ifdef UNIT_TESTS + +#include "../Individual.h" +#include "../Population.h" + +void testTransferKernels() { + + // Simple 3*3 cell-based landscape layout + const int lsDim = 3; + landParams ls_params = createDefaultLandParams(lsDim); + + // Set dispersal distances that are almost guaranteed + // to reach or fail to reach final cell + const float meanDistSuccess = static_cast(ls_params.dimX); // > 99% success + const float meanDistFailure = 0.1; // 0% success + + Landscape ls; + ls.setLandParams(ls_params, true); + + // Two suitable cells in opposite corners + Cell* init_cell = new Cell(0, 0, 0, 0); + Cell* final_cell = new Cell(ls_params.dimX - 1, ls_params.dimY - 1, 0, 0); + ls.setCellArray(); + ls.addCellToLand(init_cell); + ls.addCellToLand(final_cell); + + // Set up species + Species sp; + // Habitat codes + sp.createHabK(1); + sp.setHabK(0, 100.0); // one habitat with K = 100 + // Demography + demogrParams d; + d.stageStruct = false; + sp.setDemogr(d); + // Transfer rules + transferRules trfr; + trfr.indVar = trfr.sexDep = trfr.stgDep = false; + trfr.twinKern = trfr.distMort = false; + sp.setTrfrRules(trfr); + sp.setFullKernel(false); + + // Transfer mortality params + trfrMortParams mort; + mort.fixedMort = 0.0; + sp.setMortParams(mort); + // Settlement + settleRules sett; + sett.wait = false; + sp.setSettRules(0, 0, sett); + + // Set up patches + ls.allocatePatches(&sp); + ls.updateCarryingCapacity(&sp, 0, 0); + Patch* init_patch = init_cell->getPatch(); + + // Default distances + trfrKernelParams kern; + kern.meanDist1 = meanDistSuccess; + sp.setSpKernTraits(0, 0, kern, ls_params.resol); + Individual ind1(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); + int isDispersing = ind1.moveKernel(&ls, &sp, false); + + // After moving, individual should be in the only available cell + Cell* curr_cell = ind1.getCurrCell(); + assert(curr_cell != init_cell); + assert(curr_cell == final_cell); + assert(ind1.getStatus() == 2); // potential settler + + // If no cell within reasonable dispersal reach, individual does not move and dies + kern.meanDist1 = meanDistFailure; + sp.overrideKernels(0, 0, kern); + Individual ind2(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); // reset individual + isDispersing = ind2.moveKernel(&ls, &sp, false); + curr_cell = ind2.getCurrCell(); + assert(ind2.getStatus() == 6); // RIP in peace + assert(curr_cell == init_cell); + + // Twin kernels + trfr.twinKern = true; + sp.setTrfrRules(trfr); + kern.meanDist1 = meanDistFailure; + kern.meanDist2 = meanDistSuccess; + kern.probKern1 = 1.0; // 2nd kernel never used + sp.overrideKernels(0, 0, kern); + Individual ind3(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); + isDispersing = ind3.moveKernel(&ls, &sp, false); + assert(ind3.getStatus() == 6); // dead, could not reach destination cell + kern.probKern1 = 0.0; // always use 2nd kernel + sp.overrideKernels(0, 0, kern); + Individual ind4(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); + isDispersing = ind4.moveKernel(&ls, &sp, false); + assert(ind4.getStatus() == 2); + // reset + trfr.twinKern = false; + sp.setTrfrRules(trfr); + kern.probKern1 = 1.0; + sp.overrideKernels(0, 0, kern); + + // Sex-dependent dispersal distances + // female very unlikely to reach suitable cell + // male easily reaches suitable cell + trfr.sexDep = true; + sp.setTrfrRules(trfr); + trfrKernelParams kern_f = kern; + kern_f.meanDist1 = meanDistFailure; + sp.overrideKernels(0, 0, kern_f); + trfrKernelParams kern_m = kern; + kern_m.meanDist1 = meanDistSuccess; + sp.overrideKernels(0, 1, kern_m); + + Individual ind5(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); // female as default + isDispersing = ind5.moveKernel(&ls, &sp, false); + assert(ind5.getStatus() == 6); // dead, could not reach destination + + Individual ind6(&sp, init_cell, init_patch, 1, 0, 0, 1.0, false, 0); // male + assert(ind6.getSex() == 1); + isDispersing = ind6.moveKernel(&ls, &sp, false); + assert(ind6.getStatus() == 2); + // reset + trfr.sexDep = false; + sp.setTrfrRules(trfr); + + // Stage-dependent + trfr.stgDep = true; + sp.setTrfrRules(trfr); + trfrKernelParams kern_juv = kern; + kern_juv.meanDist1 = meanDistFailure; // juveniles very unlikely to reach suitable cell + sp.overrideKernels(0, 0, kern_juv); + trfrKernelParams kern_adult = kern; + kern_adult.meanDist1 = meanDistSuccess; // adults easily reach suitable cell + sp.overrideKernels(1, 0, kern_adult); + + Individual ind7(&sp, init_cell, init_patch, 0, 0, 0, 0.0, false, 0); // juvenile + isDispersing = ind7.moveKernel(&ls, &sp, false); + assert(ind7.getStatus() == 6); + + Individual ind8(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); // adult by default + isDispersing = ind8.moveKernel(&ls, &sp, false); + assert(ind8.getStatus() == 2); + // reset + trfr.stgDep = false; + sp.setTrfrRules(trfr); + + /* Boundaries: dispersal distance overshoots + Only adjacent cells are available + ----- + -ooo- + -oio- + -ooo- + ----- + */ + ls.resetLand(); + ls_params = createDefaultLandParams(5); + ls.setLandParams(ls_params, true); + ls.setCellArray(); // reset cells + + vector cells; + // Set central cell and all adjacent + for (int x = ls_params.minX + 1; x < ls_params.maxX; ++x) { + for (int y = ls_params.minY + 1; y < ls_params.maxY; ++y) { + cells.push_back(new Cell(x, y, 0, 0)); + } + } + + for (auto c : cells) ls.addCellToLand(c); + ls.allocatePatches(&sp); + ls.updateCarryingCapacity(&sp, 0, 0); + init_cell = cells[4]; // central cell + init_patch = init_cell->getPatch(); + + kern.meanDist1 = 100; // overshoots *most* of the time... + sp.setSpKernTraits(0, 0, kern, ls_params.resol); + Individual ind9(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); // reset individual + + // Non-absorbing boundaries + bool absorbing_boundaries{ false }; + isDispersing = ind9.moveKernel(&ls, &sp, absorbing_boundaries); + curr_cell = ind9.getCurrCell(); + assert(curr_cell != init_cell); // ...should be able to move eventually + assert(ind9.getStatus() == 2); + + // Absorbing boundaries + absorbing_boundaries = true; + Individual ind10(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); // reset individual + isDispersing = ind10.moveKernel(&ls, &sp, absorbing_boundaries); + curr_cell = ind10.getCurrCell(); + assert(ind10.getStatus() == 6); + assert(curr_cell == 0); // out of the landscape + + // Dispersal-related mortality + + // Fixed mortality + mort.fixedMort = 1.0; // Individual *will* die after any step + sp.setMortParams(mort); + trfr.distMort = false; + sp.setTrfrRules(trfr); + Individual ind11(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); + isDispersing = ind11.moveKernel(&ls, &sp, false); + assert(ind11.getStatus() == 7); + + // Distance-dependent mortality + trfr.distMort = true; + sp.setTrfrRules(trfr); + mort.mortAlpha = 1000.0; // very steep threshold + mort.mortBeta = 0.001; // very small distance + sp.setMortParams(mort); + kern.meanDist1 = 5; // very likely to go over threshold + absorbing_boundaries = true; + sp.setSpKernTraits(0, 0, kern, ls_params.resol); + Individual ind12(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); + isDispersing = ind12.moveKernel(&ls, &sp, absorbing_boundaries); + assert(ind12.getStatus() == 7); + + mort.mortBeta = 30; // very large distance, unlikely to draw + sp.setMortParams(mort); + Individual ind13(&sp, init_cell, init_patch, 1, 0, 0, 0.0, false, 0); + isDispersing = ind13.moveKernel(&ls, &sp, false); + assert(ind13.getStatus() != 7); + + // Reset mortality params + trfr.distMort = false; + mort.fixedMort = 0.0; + sp.setTrfrRules(trfr); + sp.setMortParams(mort); +} + +void testTransferCRW() { + + // Simple 5*5 cell-based landscape layout + int lsDim = 5; + landParams ls_params = createDefaultLandParams(lsDim); + double cellDiagLength = ls_params.resol * SQRT2; + + Landscape ls; + ls.setLandParams(ls_params, true); + + // All cells are suitable + const int hab_index = 0; + vector cell_vec; + for (int x = ls_params.minX; x < ls_params.dimX; ++x) { + for (int y = ls_params.minY; y < ls_params.dimY; ++y) { + cell_vec.push_back(new Cell(x, y, 0, hab_index)); + } + } + Cell* init_cell = cell_vec[12]; // central + ls.setCellArray(); + for (auto c : cell_vec) ls.addCellToLand(c); + + // Set up species + Species sp; + + // Habitat codes + sp.createHabK(1); + sp.setHabK(hab_index, 100.0); // one habitat with K = 100 + + // Habitat-dependent mortality + sp.createHabCostMort(1); + + // Transfer rules + transferRules trfr; + trfr.indVar = false; + trfr.habMort = false; + trfr.moveType = 2; // CRW + sp.setTrfrRules(trfr); + + // Transfer CRW traits + trfrMovtParams m; + m.stepMort = 0.0; + m.stepLength = cellDiagLength; // guaranteed to move out + m.rho = 1.0; + m.straightenPath = false; + sp.setSpMovtTraits(m); + + // Settlement rules + settleRules sett; + sett.wait = false; + sp.setSettRules(0, 0, sett); + settleSteps steps; + steps.maxSteps = 1; + steps.minSteps = 1; + steps.maxStepsYr = 1; + sp.setSteps(0, 0, steps); + + // Set up patches + ls.allocatePatches(&sp); + ls.updateCarryingCapacity(&sp, 0, 0); + Patch* init_patch = init_cell->getPatch(); + + // Create and set up individual + Individual ind0(&sp, init_cell, init_patch, 1, 0, 0, 0.0, true, 2); + + // Set status + assert(ind0.getStatus() == 0); // default status, not emigrating + int isDispersing = ind0.moveStep(&ls, &sp, hab_index, false); + assert(ind0.getStatus() == 0); // status didn't change + assert(ind0.getCurrCell() == init_cell); // not emigrating so didn't move + + //---------------------// + // Test per-step mortality + //---------------------// + + m.stepMort = 1.0; // should die + sp.setSpMovtTraits(m); + Individual ind1(&sp, init_cell, init_patch, 0, 0, 0, 0.0, true, 2); + // force-set path bc for some reason path gets deallocated upon exiting constructor?? + ind1.setStatus(1); + isDispersing = ind1.moveStep(&ls, &sp, hab_index, false); + // Individual begins in natal patch so mortality is disabled + assert(ind1.getStatus() != 7); + // Individual should be in a different patch + Cell* first_step_cell = ind1.getCurrCell(); + assert(first_step_cell != init_cell); + assert(first_step_cell->getPatch() != init_patch); + + ind1.setStatus(1); // emigrating again + + // Individual should die on second step + isDispersing = ind1.moveStep(&ls, &sp, hab_index, false); + assert(ind1.getCurrCell() == first_step_cell); // shouldn't have moved + assert(ind1.getStatus() == 7); // died by transfer + m.stepMort = 0.0; // not dying + sp.setSpMovtTraits(m); + + // Test habitat-depdt mortality + // ... + + //----------------// + // Test step size + //----------------// + + ls = Landscape(); + ls.setLandParams(ls_params, true); + sp.createHabK(2); + const int hab_suitable = 0; + const int hab_unsuitable = 1; + sp.setHabK(hab_suitable, 100.0); + sp.setHabK(hab_unsuitable, 0.0); + + // Initial cell unsuitable, suitable cell in opposite corner + init_cell = new Cell(0, 0, 0, hab_unsuitable); + Cell* final_cell = new Cell(ls_params.dimX - 1, ls_params.dimY - 1, 0, hab_suitable); + ls.setCellArray(); + ls.addCellToLand(init_cell); + ls.addCellToLand(final_cell); + ls.allocatePatches(&sp); + ls.updateCarryingCapacity(&sp, 0, 0); + // Init cell is NOT in natal patch + Patch* natalPatch = new Patch(0, 0); + init_patch = init_cell->getPatch(); + + // Step length is too short + m.stepLength = 0.1; // will not reach final cell + m.rho = 0.0; // random angle + sp.setSpMovtTraits(m); + steps.minSteps = 1; + steps.maxStepsYr = 2; + steps.maxSteps = 3; + sp.setSteps(0, 0, steps); + Individual ind2(&sp, init_cell, natalPatch, 0, 0, 0, 0.0, true, 2); + ind2.setStatus(1); // dispersing + // First step - still in unsuitable cell so still dispersing + isDispersing = ind2.moveStep(&ls, &sp, hab_index, false); + assert(ind2.getCurrCell() == init_cell); + assert(ind2.getStatus() == 1); // still dispersing + // Second step - reaching max steps this year, wait next year + isDispersing = ind2.moveStep(&ls, &sp, hab_index, false); + assert(ind2.getCurrCell() == init_cell); + assert(ind2.getStatus() == 3); // waiting for next year + ind2.setStatus(1); // dispersing again + // Third step - reaching max steps, dies in unsuitable cell + isDispersing = ind2.moveStep(&ls, &sp, hab_index, false); + assert(ind2.getCurrCell() == init_cell); + assert(ind2.getStatus() == 6); // died while dispersing + + // Step length too long + m.stepLength = ls_params.dimX * SQRT2 * 1.5; // overshoots + sp.setSpMovtTraits(m); + Individual ind3(&sp, init_cell, init_patch, 0, 0, 0, 0.0, true, 2); + ind3.setStatus(1); // dispersing + steps.minSteps = 1; + steps.maxStepsYr = 1; + steps.maxSteps = 1; // no need to test more than one step this time + sp.setSteps(0, 0, steps); + isDispersing = ind3.moveStep(&ls, &sp, hab_index, false); + assert(ind3.getCurrCell() == init_cell); + assert(ind3.getStatus() == 6); + + // Adequate step length + m.stepLength = (ls_params.dimX - 1) * SQRT2; + sp.setSpMovtTraits(m); + Individual ind4(&sp, init_cell, natalPatch, 0, 0, 0, 0.0, true, 2); + ind4.setStatus(1); // dispersing + // Initial angle still random but should eventually reach the suitable cell + isDispersing = ind4.moveStep(&ls, &sp, hab_index, false); + assert(ind4.getStatus() == 2); + assert(ind4.getCurrCell() == final_cell); + + // If boundaries are absorbing however, most likely to die + Individual ind5(&sp, init_cell, natalPatch, 0, 0, 0, 0.0, true, 2); + ind5.setStatus(1); // dispersing + bool absorbing_boundaries = true; + isDispersing = ind5.moveStep(&ls, &sp, hab_index, absorbing_boundaries); + assert(ind5.getStatus() == 6); + assert(ind5.getCurrCell() == 0); // deref apparently + + // Correlation parameter + // If rho = 1 should move in a straight line + // Many cells, all suitable + lsDim = 10; + ls_params = createDefaultLandParams(lsDim); + ls = Landscape(); + ls.setLandParams(ls_params, true); + cell_vec.clear(); + for (int x = ls_params.minX; x < ls_params.dimX; ++x) { + for (int y = ls_params.minY; y < ls_params.dimY; ++y) { + cell_vec.push_back(new Cell(x, y, 0, hab_suitable)); + } + } + ls.setCellArray(); + for (auto c : cell_vec) ls.addCellToLand(c); + ls.allocatePatches(&sp); + ls.updateCarryingCapacity(&sp, 0, 0); + // Individual moves by 1 along the diagonal + m.stepLength = cellDiagLength; // 1 diagonal cell + m.rho = 1; // angle = previous angle + sp.setSpMovtTraits(m); + steps.maxStepsYr = steps.maxSteps = ls_params.dimX; + sp.setSteps(0, 0, steps); + Individual ind6(&sp, cell_vec[0], natalPatch, 0, 0, 0, 0.0, true, 2); + const float diag_angle = PI / 4.0; // 45 degrees + ind6.setInitAngle(diag_angle); + // Individual moves only along diagonal cells + for (int i = 1; i < ls_params.dimX; ++i) { + ind6.setStatus(1); // dispersing + isDispersing = ind6.moveStep(&ls, &sp, hab_index, false); + assert(ind6.getStatus() == 2); + assert(ind6.getCurrCell() == cell_vec[i * (ls_params.dimX + 1)]); + } +} + +void testGenetics() { + + // Individuals inherit alleles from their parents + { + // 1 - Diploid case, 2 parents with diff. alleles + { + const int genomeSz = 5; + const bool isDiploid{ true }; + SpeciesTrait* spTr = createTestEmigSpTrait(createTestGenePositions(genomeSz), isDiploid); + + // Heterozygote parent genotypes + const float valAlleleMotherA(1.0), valAlleleMotherB(2.0); + const float valAlleleFatherA(3.0), valAlleleFatherB(4.0); + auto motherGenotype = createTestGenotype(genomeSz, isDiploid, valAlleleMotherA, valAlleleMotherB); + auto fatherGenotype = createTestGenotype(genomeSz, isDiploid, valAlleleFatherA, valAlleleFatherB); + const int startMotherChr = 0; // Strand A + const int startFatherChr = 1; // Strand B + + // Create individual trait objects + DispersalTrait dispTrParent(spTr); // initialisation constructor + DispersalTrait dispTrChild(dispTrParent); // inheritance constructor + + set recomSites{}; // no recombination + + dispTrChild.triggerInherit(true, motherGenotype, recomSites, startMotherChr); + dispTrChild.triggerInherit(false, fatherGenotype, recomSites, startFatherChr); + + // Child should have inherited strand A from mother and strand B from father + float valChildAlleleA, valChildAlleleB; + for (int i = 0; i < genomeSz; i++) { + valChildAlleleA = dispTrChild.getAlleleValueAtLocus(0, i); + assert(valChildAlleleA == valAlleleMotherA); + valChildAlleleB = dispTrChild.getAlleleValueAtLocus(1, i); + assert(valChildAlleleB == valAlleleFatherB); + } + } + + // 2 - Haploid case, simply copy parent + { + const int genomeSz = 5; + const bool isDiploid{ false }; + SpeciesTrait* spTr = createTestEmigSpTrait(createTestGenePositions(genomeSz), isDiploid); + + // Heterozygote parent genotypes + const float valAlleleMother(5.0); + auto motherGenotype = createTestGenotype(genomeSz, isDiploid, valAlleleMother); + const int startMotherChr = 999; // doesn't matter, not used + + // Create individual trait objects + DispersalTrait dispTrParent(spTr); // initialisation constructor + DispersalTrait dispTrChild(dispTrParent); // inheritance constructor + + set recomSites; + for (unsigned int i = 0; i < genomeSz; i++) + recomSites.insert(i); // recombination should be ignored + + dispTrChild.triggerInherit(true, motherGenotype, recomSites, startMotherChr); + + // Child should have inherited strand B from mother + float valChildAllele; + for (int i = 0; i < genomeSz; i++) { + valChildAllele = dispTrChild.getAlleleValueAtLocus(0, i); + assert(valChildAllele == valAlleleMother); + } + } + } + + // Recombination occurs just after selected recombination site + { + const int genomeSz = 3; + const bool isDiploid{ true }; + SpeciesTrait* spTr = createTestEmigSpTrait(createTestGenePositions(genomeSz), isDiploid); + + // Create individual trait objects + DispersalTrait dispTrParent(spTr); // initialisation constructor + + // Fill mother gene sequence + const float valAlleleA(0.0), valAlleleB(1.0); + // Mother genotype: + // 000 + // 111 + auto motherGenotype = createTestGenotype(genomeSz, isDiploid, valAlleleA, valAlleleB); + + // Trigger inheritance from mother + const vector recombinationSites{0, 1, genomeSz - 1}; // should work for any position + const int startingChr{0}; // Chromosome A + + for (unsigned int site : recombinationSites) { + DispersalTrait dispTrChild(dispTrParent); // inheritance constructor + dispTrChild.triggerInherit(true, motherGenotype, set{site}, startingChr); + // for this test we ignore inheritance from father + + // Mother-inherited alleles should have value of chr A before recombination site, chr B after + float valMotherAllele; + for (int i = 0; i < genomeSz; i++) { + valMotherAllele = dispTrChild.getAlleleValueAtLocus(0, i); + assert(valMotherAllele == (i <= site ? valAlleleA : valAlleleB)); + // don't check other chromosome, empty bc we did not resolve father inheritance + } + } + } + + // Traits that are not inherited sample their mutations in initial distribution + { + const float initialAlleleVal = 0.5; + const float parentAlleleVal = 0.8; + const bool isInherited = false; + + const int genomeSz = 5; + const bool isDiploid{ false }; + + // Create species trait + const map distParams{ + // all alleles initialised at a single value + pair{GenParamType::MIN, initialAlleleVal}, + pair{GenParamType::MAX, initialAlleleVal} + }; + + const set genePositions = createTestGenePositions(genomeSz); + + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::E_D0, + sex_t::NA, + genePositions, + ExpressionType::ADDITIVE, + genePositions, // initial positions (all) + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, not used + isInherited, + 0.0, // no mutations + DistributionType::UNIFORM, distParams, // ignored + DistributionType::NONE, distParams, // no dominance, not used + isDiploid ? 2 : 1, + false + ); + + // Create individual trait objects + DispersalTrait dispTrParent(spTr); // initialisation constructor + auto parentGenotype = createTestGenotype(genomeSz, isDiploid, parentAlleleVal); + const int startMotherChr = 999; // doesn't matter, not used + dispTrParent.overwriteGenes(parentGenotype); + assert(dispTrParent.getAlleleValueAtLocus(0, 0) == parentAlleleVal); + + DispersalTrait dispTrChild(dispTrParent); // inheritance constructor + + set recomSites{genomeSz - 1}; + dispTrChild.triggerInherit(true, parentGenotype, recomSites, startMotherChr); + + // Child does not inherit from mother, + // instead allele value is initial value + float valChildAllele; + for (int i = 0; i < genomeSz; i++) { + valChildAllele = dispTrChild.getAlleleValueAtLocus(0, i); + assert(valChildAllele == initialAlleleVal); + } + } + + // Genetic fitness mutations are constrained between -1 or 1 + // + sampled dominance coefficients are always positive + { + { + // Most likely (~96%) to sample a mutation > 1 + const float gammaMutShapeParam = 5.0; + const float gammaMutScaleParam = 1.0; + // Normal centered on 0 : ~50% of sampling negative dominance coefficient + const float dominanceMeanParam = 0.0; + const float dominanceSdParam = 1.0; + + const int genomeSz = 5; + const bool isDiploid{ false }; + + // Create species trait + const map mutationParams{ + pair{GenParamType::SHAPE, gammaMutShapeParam}, + pair{GenParamType::SCALE, gammaMutScaleParam} + }; + const map dominanceParams{ + pair{GenParamType::MEAN, dominanceMeanParam}, + pair{GenParamType::SD, dominanceSdParam} + }; + const map placeholderParams = mutationParams; + + const set genePositions = createTestGenePositions(genomeSz); + + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::GENETIC_LOAD1, + sex_t::NA, + genePositions, + ExpressionType::MULTIPLICATIVE, + genePositions, + DistributionType::NONE, placeholderParams, // not used for genetic load + DistributionType::NONE, dominanceParams, + true, + 1.0, // every site mutates + DistributionType::GAMMA, mutationParams, + DistributionType::NORMAL, dominanceParams, + isDiploid ? 2 : 1, + false + ); + + // Create individual trait object + GeneticFitnessTrait traitInd(spTr); // initialisation constructor + traitInd.mutate(); + for (int i = 0; i < genomeSz; i++) { + float valAllele = traitInd.getAlleleValueAtLocus(0, i); + assert(valAllele <= 1.0); + float domCoef = traitInd.getDomCoefAtLocus(0, i); + assert(domCoef >= 0.0); + } + } + + { + // 1/2 chance to sample a mutation < -1 + const float normalMutMeanParam = 0; + const float normalMutSdParam = 1.0; + + const int genomeSz = 10; + const bool isDiploid{ false }; + + // Create species trait + const map mutationParams{ + // all alleles initialised at a single value + pair{GenParamType::MEAN, normalMutMeanParam}, + pair{GenParamType::SD, normalMutSdParam} + }; + const map placeholderParams = mutationParams; + + const set genePositions = createTestGenePositions(genomeSz); + + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::GENETIC_LOAD1, + sex_t::NA, + genePositions, + ExpressionType::MULTIPLICATIVE, + genePositions, + DistributionType::NONE, placeholderParams, // not used for genetic load + DistributionType::NONE, placeholderParams, // doesn't matter for this test + true, + 1.0, // every site mutates + DistributionType::NORMAL, mutationParams, + DistributionType::NORMAL, placeholderParams, // doesn't matter for this test + isDiploid ? 2 : 1, + false + ); + + // Create individual trait object + GeneticFitnessTrait traitInd(spTr); // initialisation constructor + traitInd.mutate(); + for (int i = 0; i < genomeSz; i++) { + float valAllele = traitInd.getAlleleValueAtLocus(0, i); + assert(valAllele > -1.0); + } + } + } +} + +bool haveSameEmigD0Allele(const Individual& indA, const Individual& indB, const int& position, short whichHaplo = 0) { + return indA.getTrait(E_D0)->getAlleleValueAtLocus(whichHaplo, position) + == indB.getTrait(E_D0)->getAlleleValueAtLocus(whichHaplo, position); +} + +void testIndividual() { + + // Kernel-based transfer + testTransferKernels(); + + // Correlated random walk (CRW) + testTransferCRW(); + + testGenetics(); + + // Genetic linkage + Chromosome breaks + // Considering diallelic genes A, B, C, D with: + // A, B, C are on chr.1, D is on chr.2 + // A, B are adjacent, C sits on the other end of chr.1 + // C, D have adjacent positions in genome (but are on separate chr.) + // AB------------C//D + // We simulate 100 inheritance + recombination processes and expect that: + // 1. freq(A,B have same alleles) >> freq(A,C have same alleles) + // 2. 0.65 > freq(C,D have same alleles) > 0.35 despite being adjacent because of chrom. break + // (both freq. have p < 0.001 from a binomial with p 0.5 and 100 trials) + { + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + const float recombinationRate = 0.01; + const int genomeSz = 10; + + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz-2, genomeSz - 1}, // two chromosomes + genomeSz, + recombinationRate, + set{}, "none", set{}, 0 // no output so no sampling + ); + + int posA = 0; + int posB = 1; + int posC = genomeSz - 2; + int posD = genomeSz - 1; + const set genePositions = {posA, posB, posC, posD}; + const bool isDiploid{ true }; + SpeciesTrait* spTr = createTestEmigSpTrait(genePositions, isDiploid); + pSpecies->addTrait(TraitType::E_D0, *spTr); + + Individual indMother = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, false, 0); + Individual indFather = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 1.0, false, 0); + indMother.setUpGenes(pSpecies, 1.0); + indFather.setUpGenes(pSpecies, 1.0); + + int countRecombineTogetherAB = 0; + int countRecombineTogetherAC = 0; + int countRecombineTogetherCD = 0; + + const int nbTrials = 100; + for (int i = 0; i < nbTrials; ++i) + { + Individual indChild = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, false, 0); + indChild.inheritTraits(pSpecies, &indMother, &indFather, 1.0); + + bool hasInheritedA0 = haveSameEmigD0Allele(indChild, indMother, posA); + bool hasInheritedB0 = haveSameEmigD0Allele(indChild, indMother, posB); + bool hasInheritedC0 = haveSameEmigD0Allele(indChild, indMother, posC); + bool hasInheritedD0 = haveSameEmigD0Allele(indChild, indMother, posD); + + countRecombineTogetherAB += (hasInheritedA0 && hasInheritedB0) + || (!hasInheritedA0 && !hasInheritedB0); + countRecombineTogetherAC += (hasInheritedA0 && hasInheritedC0) + || (!hasInheritedA0 && !hasInheritedC0); + countRecombineTogetherCD += (hasInheritedC0 && hasInheritedD0) + || (!hasInheritedC0 && !hasInheritedD0); + } + assert(countRecombineTogetherAB > countRecombineTogetherAC); + assert(35 < countRecombineTogetherCD && countRecombineTogetherCD < 65); + } + + // Sex-specific traits use the correct genes + /// Set up a sex-dependent emigration probability with male and female loci + /// Emigration probability is 1 initially, but female trait mutates. + { + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + // Species-level paramters + const int genomeSz = 6; + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // one chromosome + genomeSz, + 0.0, // no recombination + set{}, "none", set{}, 0 // no output so no sampling + ); + emigRules emig; + emig.indVar = true; + emig.sexDep = true; + emig.densDep = false; + pSpecies->setEmigRules(emig); + + // Create species trait + //// Shared params + const map initParams{ + // all values are 1 + pair{GenParamType::MIN, 1.0}, + pair{GenParamType::MAX, 1.0} + }; + const map mutationParams{ + // reduction of emigration probability + pair{GenParamType::MIN, -0.5}, + pair{GenParamType::MAX, -0.25} + }; + const bool isDiploid{ true }; + + //// Sex-specific params + const set maleGenePositions = { 0, 2, 4 }; + const set femaleGenePositions = { 1, 3, 5 }; + const float maleMutationRate = 0.0; + const float femaleMutationRate = 1.0; + + SpeciesTrait* spTrM = new SpeciesTrait( + TraitType::E_D0_M, + sex_t::MAL, + maleGenePositions, + ExpressionType::AVERAGE, + maleGenePositions, + // Set all initial alleles values to 1 + DistributionType::UNIFORM, initParams, + DistributionType::NONE, initParams, // no dominance, params are ignored + true, // isInherited + maleMutationRate, // does not mutate + DistributionType::UNIFORM, mutationParams, // not used + DistributionType::NONE, initParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + pSpecies->addTrait(TraitType::E_D0_M, *spTrM); + + SpeciesTrait* spTrF = new SpeciesTrait( + TraitType::E_D0_F, + sex_t::FEM, + femaleGenePositions, + ExpressionType::AVERAGE, + femaleGenePositions, + // Set all initial alleles values to 1 + DistributionType::UNIFORM, initParams, + DistributionType::NONE, initParams, // no dominance, params are ignored + true, // isInherited + femaleMutationRate, // does mutate + DistributionType::UNIFORM, mutationParams, // not used + DistributionType::NONE, initParams, + isDiploid ? 2 : 1, + false + ); + pSpecies->addTrait(TraitType::E_D0_F, *spTrF); + + // Set up male and female individuals, trigger mutations + Individual indFemale = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, false, 0); + Individual indMale = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 1.0, false, 0); + indFemale.setUpGenes(pSpecies, 1.0); + indMale.setUpGenes(pSpecies, 1.0); + indFemale.triggerMutations(pSpecies); + indMale.triggerMutations(pSpecies); + + // Male should use male trait, still 1 + // Female should use female trait, has mutated + emigTraits femaleEmig = indFemale.getIndEmigTraits(); + emigTraits maleEmig = indMale.getIndEmigTraits(); + assert(femaleEmig.d0 != 1.0); + assert(maleEmig.d0 == 1.0); + } + + // Individuals use species-level trait when not individual variable, + // and individual-level trait when trait is individual variable + { + float spEmigProb = 1.0; + float indEmigProb = 0.0; + + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + // Species-level paramters + const int genomeSz = 1; + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // one chromosome + genomeSz, + 0.0, // no recombination + set{}, "none", set{}, 0 // no output so no sampling + ); + emigRules emig; + emig.indVar = false; + emig.stgDep = false; emig.sexDep = false; emig.densDep = false; + pSpecies->setEmigRules(emig); + + emigTraits spEmigTraits; spEmigTraits.d0 = spEmigProb; + pSpecies->setSpEmigTraits(0, 0, spEmigTraits); + + // Create species trait + const map initParams{ + // Emigration probability is always 0 + pair{GenParamType::MIN, indEmigProb}, + pair{GenParamType::MAX, indEmigProb} + }; + const map mutationParams = initParams; // doesn't matter, not used + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::E_D0, + sex_t::NA, + set{ 0 }, // only one locus + ExpressionType::AVERAGE, + set{ 0 }, // initial positions + DistributionType::UNIFORM, initParams, + DistributionType::NONE, initParams, // no dominance, params are ignored + true, // isInherited + 0.0, // no mutation + DistributionType::UNIFORM, mutationParams, // not used + DistributionType::NONE, initParams, // no dominance, params are ignored + 2, // diploid + false + ); + pSpecies->addTrait(TraitType::E_D0, *spTr); + + Individual ind = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, false, 0); + ind.setUpGenes(pSpecies, 1.0); + + // Create population to trigger emigration selection + Population pop(pSpecies, pPatch, 0, 1.0); + pop.recruit(&ind); + assert(ind.getStatus() == 0); + pop.emigration(100.0); + + // Individual is using the species-wide emigration prob, + // so should be selected to emigrate (status 1) + assert(ind.getStatus() == 1); + + // Change rules to use individual-variable trait + ind.setStatus(0); + emig.indVar = true; + pSpecies->setEmigRules(emig); + pop.emigration(100.0); + + // Individual-level emig prob is zero, must not be emigrating; + assert(ind.getStatus() == 0); + + pop.clearInds(); // empty inds vector to not deallocate individual twice + } + + // Individuals with genetic fitness = 1 are always viable + // Individuals with genetic fitness = 0 are never viable + { + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + // Species-level paramters + const int genomeSz = 1; + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // one chromosome + genomeSz, + 0.0, // no recombination + set{}, "none", set{}, 0 // no output so no sampling + ); + + // Create species trait + const map mutationParams{ + // Always sample 1 = a lethal mutation + pair{GenParamType::MIN, 1.0}, + pair{GenParamType::MAX, 1.0} + }; + const map domParams = mutationParams; // all dominance parameters are equal + const map initParams = mutationParams; // doesn't matter, not used + + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::GENETIC_LOAD1, + sex_t::NA, + set{ 0 }, // only one locus + ExpressionType::MULTIPLICATIVE, + set{ 0 }, + DistributionType::NONE, initParams, + DistributionType::UNIFORM, domParams, + true, // isInherited + 1.0, // will mutate + DistributionType::UNIFORM, mutationParams, // lethal mutation + DistributionType::UNIFORM, domParams, + 2, // diploid + false + ); + + pSpecies->addTrait(TraitType::GENETIC_LOAD1, *spTr); + + Individual ind = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, false, 0); + ind.setUpGenes(pSpecies, 1.0); + + // By default, all loci are initialised at 0 so individual is viable + assert(ind.getGeneticFitness() == 1.0); + assert(ind.isViable()); + + ind.triggerMutations(pSpecies); + + // Individual now bears a lethal allele + assert(ind.getGeneticFitness() == 0.0); + assert(!ind.isViable()); + } + + // A largely dominant allele overrides the expression of its homologue + { + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + // Species-level paramters + const int genomeSz = 1; + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // one chromosome + genomeSz, + 0.0, // no recombination + set{}, "none", set{}, 0 // no output so no sampling + ); + + // Create template species trait + const map distParams{ + pair{GenParamType::MIN, 0.0}, + pair{GenParamType::MAX, 0.0} + }; + // Pretty empty, actual values are set below + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::GENETIC_LOAD1, + sex_t::NA, + set{ 0 }, // only one locus + ExpressionType::MULTIPLICATIVE, + set{ 0 }, + DistributionType::NONE, distParams, + DistributionType::UNIFORM, distParams, + true, // isInherited + 0.0, // no mutation + DistributionType::UNIFORM, distParams, + DistributionType::UNIFORM, distParams, + 2, // diploid + false + ); + pSpecies->addTrait(TraitType::GENETIC_LOAD1, *spTr); + + Individual ind = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, false, 0); + ind.setUpGenes(pSpecies, 1.0); + + const float valAlleleA = 1.0; // lethal + const float valAlleleB = 0.0; // mild + float domCoefA = 0.00001; // highly recessive + float domCoefB = 1.0; + auto viableGenotype = createTestGenotype(genomeSz, true, valAlleleA, valAlleleB, domCoefA, domCoefB); + ind.overrideGenotype(GENETIC_LOAD1, viableGenotype); + ind.triggerMutations(pSpecies); // no mutations (rate = 0) but updates genetic fitness + assert(ind.isViable()); + + domCoefA = 10000.0; // oh gosh now it's lethal AND dominant + auto lethalGenotype = createTestGenotype(genomeSz, true, valAlleleA, valAlleleB, domCoefA, domCoefB); + ind.overrideGenotype(GENETIC_LOAD1, lethalGenotype); + ind.triggerMutations(pSpecies); + assert(!ind.isViable()); + } + + // Dispersal trait alleles can take any value, but + // phenotypes are constrained to valid values + { + // Case 1 - Settlement + Kernel-based transfer + { + const int genomeSz = 4; + + const bool isDiploid{ true }; // haploid, simpler check + const float mutationRate = 0.0; // no mutations + + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + // Genome-level settings + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // one chromosome + genomeSz, + 0.0, // no recombination + set{}, "none", set{}, 0 // no output so no sampling + ); + + settleType sett; + sett.indVar = true; + sett.sexDep = false; + sett.stgDep = false; + pSpecies->setSettle(sett); + settleRules settRules; + settRules.densDep = true; + pSpecies->setSettRules(0, 0, settRules); + + transferRules trfr; + trfr.indVar = true; + trfr.usesMovtProc = false; + trfr.moveType = 0; // kernels + trfr.twinKern = false; + trfr.sexDep = false; + trfr.stgDep = false; + pSpecies->setTrfrRules(trfr); + + // Species-level traits + const map distParams{ + // prameters don't matter, allele values are overwritten below + pair{GenParamType::MIN, 1.0}, + pair{GenParamType::MAX, 1.0} + }; + SpeciesTrait* trSettProb = new SpeciesTrait( + TraitType::S_S0, + sex_t::NA, + set{ 0 }, + ExpressionType::AVERAGE, + set{ 0 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + SpeciesTrait* trSettAlpha = new SpeciesTrait( + TraitType::S_ALPHA, + sex_t::NA, + set{ 1 }, + ExpressionType::AVERAGE, + set{ 1 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, + isDiploid ? 2 : 1, + false + ); + SpeciesTrait* trSettBeta = new SpeciesTrait( + TraitType::S_BETA, + sex_t::NA, + set{ 2 }, + ExpressionType::AVERAGE, + set{ 2 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + SpeciesTrait* trMeanKern = new SpeciesTrait( + TraitType::KERNEL_MEANDIST_1, + sex_t::NA, + set{ 3 }, + ExpressionType::ADDITIVE, + set{ 3 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + + pSpecies->addTrait(TraitType::S_S0, *trSettProb); + pSpecies->addTrait(TraitType::S_ALPHA, *trSettAlpha); + pSpecies->addTrait(TraitType::S_BETA, *trSettBeta); + pSpecies->addTrait(TraitType::KERNEL_MEANDIST_1, *trMeanKern); + + Individual ind = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, false, 0); + ind.setUpGenes(pSpecies, 1.0); + + // Overwrite genotypes with alleles resulting in invalid phenotypes + auto sProbGenotype = createTestGenotype(genomeSz, true, 1.1, 1.2); + auto sAlphaGenotype = createTestGenotype(genomeSz, true, -1.5, -0.5); + auto kernelDistGenotype = createTestGenotype(genomeSz, true, -0.2, -0.4); + ind.overrideGenotype(S_S0, sProbGenotype); + ind.overrideGenotype(S_ALPHA, sAlphaGenotype); + ind.overrideGenotype(KERNEL_MEANDIST_1, kernelDistGenotype); + // any value is valid for settlement beta + + ind.triggerMutations(pSpecies); // no mutations, but trigger expression + + settleTraits settTr = ind.getIndSettTraits(); + assert(settTr.s0 <= 1.0); + assert(settTr.alpha > 0.0); + kernelData trfrTr = *(static_cast(ind.getTrfrData())); + assert(trfrTr.meanDist1 >= 0.0); + } + + // Case 2 - Correlated random walk transfer + { + const int genomeSz = 2; + + const bool isDiploid{ true }; // haploid, simpler check + const float mutationRate = 0.0; // no mutations + + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + // Genome-level settings + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // one chromosome + genomeSz, + 0.0, // no recombination + set{}, "none", set{}, 0 // no output so no sampling + ); + + settleType sett; + sett.indVar = false; + pSpecies->setSettle(sett); + + transferRules trfr; + trfr.indVar = true; + trfr.usesMovtProc = true; + trfr.moveType = 2; // CRW + pSpecies->setTrfrRules(trfr); + + // Species-level traits + const map distParams{ + // prameters don't matter, allele values are overwritten below + pair{GenParamType::MIN, 1.0}, + pair{GenParamType::MAX, 1.0} + }; + SpeciesTrait* trCRWLen = new SpeciesTrait( + TraitType::CRW_STEPLENGTH, + sex_t::NA, + set{ 0 }, + ExpressionType::ADDITIVE, + set{ 0 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + SpeciesTrait* trCRWCorr = new SpeciesTrait( + TraitType::CRW_STEPCORRELATION, + sex_t::NA, + set{ 1 }, + ExpressionType::AVERAGE, + set{ 1 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + + pSpecies->addTrait(TraitType::CRW_STEPLENGTH, *trCRWLen); + pSpecies->addTrait(TraitType::CRW_STEPCORRELATION, *trCRWCorr); + + bool usesMovtProcess = true; + short whichMovtProcess = 2; // CRW + Individual ind = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, usesMovtProcess, whichMovtProcess); + ind.setUpGenes(pSpecies, 1.0); + + // Overwrite genotypes with alleles resulting in invalid phenotypes + auto crwLenGenotype = createTestGenotype(genomeSz, true, -1.1, -1.2); + auto crwCorrGenoType = createTestGenotype(genomeSz, true, 1.5, 2.0); + ind.overrideGenotype(CRW_STEPLENGTH, crwLenGenotype); + ind.overrideGenotype(CRW_STEPCORRELATION, crwCorrGenoType); + + ind.triggerMutations(pSpecies); // no mutations, but trigger expression + + crwData trfrTr = *(static_cast(ind.getTrfrData())); + assert(trfrTr.stepLength >= 0.0); + assert(trfrTr.rho <= 1.0); + } + + // Case 3 - Transfer with Stochastic Movement Simulator + { + const int genomeSz = 2; + const bool isDiploid{ true }; // haploid, simpler check + const float mutationRate = 0.0; // no mutations + + Patch* pPatch = new Patch(0, 0); + Cell* pCell = new Cell(0, 0, pPatch, 0); + + // Genome-level settings + Species* pSpecies = createDefaultSpecies(); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // one chromosome + genomeSz, + 0.0, // no recombination + set{}, "none", set{}, 0 // no output so no sampling + ); + + settleType sett; + sett.indVar = false; + pSpecies->setSettle(sett); + + transferRules trfr; + trfr.indVar = true; + trfr.usesMovtProc = true; + trfr.moveType = 1; // SMS + pSpecies->setTrfrRules(trfr); + + // Species-level traits + const map distParams{ + // prameters don't matter, allele values are overwritten below + pair{GenParamType::MIN, 1.0}, + pair{GenParamType::MAX, 1.0} + }; + SpeciesTrait* trSmsDirPers = new SpeciesTrait( + TraitType::SMS_DP, + sex_t::NA, + set{ 0 }, + ExpressionType::ADDITIVE, + set{ 0 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + SpeciesTrait* trSmsGoalBias = new SpeciesTrait( + TraitType::SMS_GB, + sex_t::NA, + set{ 1 }, + ExpressionType::AVERAGE, + set{ 1 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + SpeciesTrait* trSmsAlpha = new SpeciesTrait( + TraitType::SMS_ALPHADB, + sex_t::NA, + set{ 0 }, + ExpressionType::ADDITIVE, + set{ 0 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + SpeciesTrait* trSmsBeta = new SpeciesTrait( + TraitType::SMS_BETADB, + sex_t::NA, + set{ 1 }, + ExpressionType::AVERAGE, + set{ 1 }, + DistributionType::UNIFORM, distParams, + DistributionType::NONE, distParams, // no dominance, params are ignored + true, // isInherited + mutationRate, // does not mutate + DistributionType::UNIFORM, distParams, // not used + DistributionType::NONE, distParams, // no dominance, params are ignored + isDiploid ? 2 : 1, + false + ); + + pSpecies->addTrait(TraitType::SMS_DP, *trSmsDirPers); + pSpecies->addTrait(TraitType::SMS_GB, *trSmsGoalBias); + pSpecies->addTrait(TraitType::SMS_ALPHADB, *trSmsAlpha); + pSpecies->addTrait(TraitType::SMS_BETADB, *trSmsBeta); + + bool usesMovtProcess = true; + short whichMovtProcess = 1; // SMS + Individual ind = Individual(pSpecies, pCell, pPatch, 0, 0, 0, 0.0, usesMovtProcess, whichMovtProcess); + ind.setUpGenes(pSpecies, 1.0); + + // Overwrite genotypes with alleles resulting in invalid phenotypes + auto smsDPGenotype = createTestGenotype(genomeSz, true, 0.1, 0.2); + auto smsGBGenotype = createTestGenotype(genomeSz, true, 0.5, 0.0); + auto smsAlphaGenotype = createTestGenotype(genomeSz, true, 0.0, 0.0); + ind.overrideGenotype(SMS_DP, smsDPGenotype); + ind.overrideGenotype(SMS_GB, smsGBGenotype); + ind.overrideGenotype(SMS_ALPHADB, smsAlphaGenotype); + // any value is valid for SMS beta + + ind.triggerMutations(pSpecies); // no mutations, but trigger expression + + smsData trfrTr = *(static_cast(ind.getTrfrData())); + assert(trfrTr.dp >= 1.0); + assert(trfrTr.gb >= 1.0); + assert(trfrTr.alphaDB > 0.0); + } + } + +} + +#endif // UNIT_TESTS diff --git a/unit_tests/testNeutralStats.cpp b/unit_tests/testNeutralStats.cpp new file mode 100644 index 0000000..50999c2 --- /dev/null +++ b/unit_tests/testNeutralStats.cpp @@ -0,0 +1,1072 @@ +#ifdef UNIT_TESTS + +#include "../Community.h" + +void testNeutralStats() { + + // In haploid settings, Ho is always zero + { + // Create empty 1-patch, 1-cell landscape + Landscape* pLandscape = new Landscape; + Patch* pPatch = pLandscape->newPatch(0); + const set patchList{ pPatch->getPatchNum() }; + const string indSampling = "all"; + const set stgToSample = { 1 }; + Cell* pCell = new Cell(0, 0, pPatch, 0); + pPatch->addCell(pCell, 0, 0); + + // Create genetic structure + const int genomeSz = 4; + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultHaploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const set genePositions = { 0, 1, 3 }; // arbitrary + const bool isDiploid{ false }; + const float maxAlleleVal = 0; // sample in uniform(0,0) so every individual is homozygote + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise population + const int nbInds = 2; + Population* pPop = new Population(pSpecies, pPatch, nbInds, 1); + pPop->sampleIndsWithoutReplacement(indSampling, stgToSample); + + // Calculate heterozygosity + auto pNeutralStatistics = make_unique(patchList.size(), genePositions.size()); + pNeutralStatistics->calculateHo( + patchList, + nbInds, + genePositions.size(), + pSpecies, + pLandscape + ); + assert(pNeutralStatistics->getHo() == 0.0); + } + + // If every individual in a sample is homozygote, Ho is zero + { + // Create empty 1-patch, 1-cell landscape + Landscape* pLandscape = new Landscape; + Patch* pPatch = pLandscape->newPatch(0); + const set patchList{ pPatch->getPatchNum() }; + const string indSampling = "all"; + const set stgToSample = { 1 }; + Cell* pCell = new Cell(0, 0, pPatch, 0); + pPatch->addCell(pCell, 0, 0); + + // Create genetic structure + const int genomeSz = 4; + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const set genePositions = { 0, 1, 3 }; // arbitrary + const bool isDiploid{ true }; + const float maxAlleleVal = 0; // sample in uniform(0,0) so every individual is homozygote + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise population + const int nbInds = 2; + Population* pPop = new Population(pSpecies, pPatch, nbInds, 1); + pPop->sampleIndsWithoutReplacement(indSampling, stgToSample); + + // Calculate heterozygosity + auto pNeutralStatistics = make_unique(patchList.size(), genePositions.size()); + pNeutralStatistics->calculateHo( + patchList, + nbInds, + genePositions.size(), + pSpecies, + pLandscape + ); + assert(pNeutralStatistics->getHo() == 0.0); + } + + // If every individual in a sample is heterozygote, Ho is one. + { + // Create empty 1-patch, 1-cell landscape + Landscape* pLandscape = new Landscape; + Patch* pPatch = pLandscape->newPatch(0); + const set patchList{ pPatch->getPatchNum() }; + const string indSampling = "all"; + const set stgToSample = { 1 }; + Cell* pCell = new Cell(0, 0, pPatch, 0); + pPatch->addCell(pCell, 0, 0); + + // Create genetic structure + const int genomeSz = 4; + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const set genePositions = { 0, 2 }; // arbitrary + const bool isDiploid{ true }; + const float maxAlleleVal = 255; // highly unlikely that same value sampled twice + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise population + const int nbInds = 2; + Population* pPop = new Population(pSpecies, pPatch, nbInds, 1); + pPop->sampleIndsWithoutReplacement(indSampling, stgToSample); + + // Calculate heterozygosity + auto pNeutralStatistics = make_unique(patchList.size(), genePositions.size()); + pNeutralStatistics->calculateHo( + patchList, + nbInds, + genePositions.size(), + pSpecies, + pLandscape + ); + assert(pNeutralStatistics->getHo() == 1.0); + } + + // The correct number of individuals is sampled + { + const int nbIndsPopA = 15; + const int nbIndsPopB = 11; + const string indSampling = "13"; + const set stgToSample = { 1 }; + + // Patch setup + const int nbPatches = 2; + // Genetic setup + const int genomeSz = 2; + const bool isDiploid{ true }; + const set genePositions = { 0, 1 }; + const float maxAlleleVal = 1; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + Population* pPopA = new Population(pSpecies, patches[0], nbIndsPopA, 1); + Population* pPopB = new Population(pSpecies, patches[1], nbIndsPopB, 1); + pPopA->sampleIndsWithoutReplacement(indSampling, { indStg }); + pPopB->sampleIndsWithoutReplacement(indSampling, { indStg }); + + assert(pPopA->sampleSize() == stoi(indSampling)); // the correct nb of individuals has been sampled + assert(pPopB->sampleSize() == nbIndsPopB); // too small; all individuals have been sampled + } + + // If the sample is too small, Fst evaluates to zero + // More importantly, the neutral stats module still runs without error + { + // 1 - if there is zero population in the sample + { + // Patch setup + const int nbPatches = 2; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + const bool isDiploid = true; + const set genePositions = { 0 }; + const float maxAlleleVal = 0; + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Oops, no populations in patches + + // Compute F-stats + const int nbLoci = 0; + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = 0; + pNeutralStatistics->calculateFstatWC( + patchList, + 0, + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() == 0.0); + } // end case 1 - zero pop in sample + + // 2 - If there is only one population in the sample + { + // Patch setup + const int nbPatches = 2; + const int nbPops = nbPatches - 1; // oops, 1 population is missing + const int nbIndsPerPop = 5; + // Genetic setup + const int genomeSz = 2; + const bool isDiploid{ true }; + const set genePositions = { 0, 1 }; + const float maxAlleleVal = 0; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < nbPops; p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop * patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() == 0.0); + } // end case 2, only one population in sample + + // 3 - Two empty populations + { + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 0; // oops + + // Genetic setup + const int genomeSz = 2; + const bool isDiploid{ true }; + const set genePositions = { 0, 1 }; + const float maxAlleleVal = 0; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < nbPatches; p++) { + Population* pPop = new Population(pSpecies, patches[p], nbIndsPerPop, 1); + // create individuals and add to pop + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop * patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() == 0.0); + } + } + + // If two sampled pops are monomorphic for different alleles, + // then Fst is 1 + { + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 2; + // Genetic setup + const int genomeSz = 2; + const bool isDiploid{ true }; + const set genePositions = { 0, 1 }; + const float maxAlleleVal = 1; + unsigned char alleleValPopA = char(0); + unsigned char alleleValPopB = char(1); + auto popAGenotype = createTestNeutralGenotype(genomeSz, true, alleleValPopA, alleleValPopA); + auto popBGenotype = createTestNeutralGenotype(genomeSz, true, alleleValPopB, alleleValPopB); + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + vector>> genotypes = { popAGenotype, popBGenotype }; + const int indStg = 1; + for (int p = 0; p < patches.size(); p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pInd->overrideGenotype(NEUTRAL, genotypes[p]); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop * patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() == 1.0); + +// pNeutralStatistics->calcPairwiseWeightedFst( +// patchList, +// nbIndsPerPop* patchList.size(), +// nbLoci, +// pSpecies, +// pLandscape +// ); +// assert(pNeutralStatistics->getPairwiseFst(0, 1) == 0.0); + } + + double refWeirCockerhamDiploidFst; // for use in further tests below + + // In strictly homozygote samples: + // 1. Fis = 1 (maximum inbreeding) + // 2. The sign of the Fst depends on the ratio of variation within vs between populations + // 3. (if sample sizes are equal) The Weir&Cockerham 1984, Weir&Hill 2002 estimators yield the same values + { + // Case 1/2: variation within > between + // Within: frequencies of allele A and B are quite different + // Between: no variation + { + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 10; + // Genetic setup + const int genomeSz = 1; + const bool isDiploid{ true }; + const set genePositions = { 0 }; + const float maxAlleleVal = 1; + unsigned char alleleValPopA = char(0); + unsigned char alleleValPopB = char(1); + const auto genotypeAA = createTestNeutralGenotype(genomeSz, true, alleleValPopA, alleleValPopA); + const auto genotypeBB = createTestNeutralGenotype(genomeSz, true, alleleValPopB, alleleValPopB); + vector>> genotypes = { + // 8 AA, 2 BB (no heterozygotes) + genotypeAA, genotypeAA, genotypeAA, genotypeAA, genotypeBB, + genotypeAA, genotypeAA, genotypeAA, genotypeAA, genotypeBB + }; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < patches.size(); p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pInd->overrideGenotype(NEUTRAL, genotypes[i]); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop * patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() < 0.0); + assert(pNeutralStatistics->getFisWC() == 1.0); + + // pNeutralStatistics->calcPairwiseWeightedFst( + // patchList, + // nbIndsPerPop * patchList.size(), + // nbLoci, + // pSpecies, + // pLandscape + // ); + // const double tol = 0.000001; + // assert(abs(pNeutralStatistics->getWeightedFst() - pNeutralStatistics->getFstWC()) < tol); + + refWeirCockerhamDiploidFst = pNeutralStatistics->getFstWC(); // for use in further tests below + } + + // Case 2/2: variation within < between + // Within: no variation for pop1, same allelic frequencies + // Between: larger variation + { + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 10; + // Genetic setup + const int genomeSz = 1; + const bool isDiploid{ true }; + const set genePositions = { 0 }; + const float maxAlleleVal = 1; + unsigned char alleleValPopA = char(0); + unsigned char alleleValPopB = char(1); + const auto genotypeAA = createTestNeutralGenotype(genomeSz, true, alleleValPopA, alleleValPopA); + const auto genotypeBB = createTestNeutralGenotype(genomeSz, true, alleleValPopB, alleleValPopB); + vector>>> genotypeList = { + { // Pop 1: 5 AA, 5 BB (no heterozygotes) + genotypeAA, genotypeAA, genotypeAA, genotypeAA, genotypeAA, + genotypeBB, genotypeBB, genotypeBB, genotypeBB, genotypeBB + }, + { // Pop 2: 8 AA, 2 BB (no heterozygotes) + genotypeAA, genotypeAA, genotypeAA, genotypeAA, genotypeAA, + genotypeAA, genotypeAA, genotypeAA, genotypeBB, genotypeBB + } + }; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < patches.size(); p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pInd->overrideGenotype(NEUTRAL, genotypeList[p][i]); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop* patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() > 0.0); + assert(pNeutralStatistics->getFisWC() == 1.0); + + // // Weir & Hill population-specific estimates average to the (Weir & Hill) global estimator + // pNeutralStatistics->calcPairwiseWeightedFst( + // patchList, + // nbIndsPerPop* patchList.size(), + // nbLoci, + // pSpecies, + // pLandscape + // ); + // const double pop1Fst = pNeutralStatistics->getPairwiseFst(0, 0); + // const double pop2Fst = pNeutralStatistics->getPairwiseFst(1, 1); + // assert((pop1Fst + pop2Fst) / 2.0 == pNeutralStatistics->getWeightedFst()); + + } + } + + // In a strictly heterozygote sample, + // 1. Fis = -1 + // 2. If there is no variation between individuals, Fst = 0 + // 3. Weir & Cockerham 1984 estimator > Weir & Hill 2002 + { + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 10; + // Genetic setup + const int genomeSz = 1; + const bool isDiploid{ true }; + const set genePositions = { 0 }; + const float maxAlleleVal = 1; + unsigned char alleleValPopA = char(0); + unsigned char alleleValPopB = char(1); + const auto genotypeAB = createTestNeutralGenotype(genomeSz, true, alleleValPopA, alleleValPopB); + // all individuals have genotype AB + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < patches.size(); p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pInd->overrideGenotype(NEUTRAL, genotypeAB); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop* patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() == 0.0); + assert(pNeutralStatistics->getFisWC() == -1.0); + + // pNeutralStatistics->calcPairwiseWeightedFst( + // patchList, + // nbIndsPerPop* patchList.size(), + // nbLoci, + // pSpecies, + // pLandscape + // ); + // assert(pNeutralStatistics->getWeightedFst() < pNeutralStatistics->getFstWC()); + // Weir and Hill is still equal to Weir and Cockerham full homozygote case + // const double tol = 0.000001; + // assert(abs(pNeutralStatistics->getWeightedFst() - refWeirCockerhamDiploidFst) < tol); + } + + // Fst calculation is correct for an ordinary sample + { + // Two 10-individual pops, + // 1 tri-allelic locus + + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 10; + + // Genetic setup + const int genomeSz = 1; + const bool isDiploid{ true }; + const set genePositions = { 0 }; + const float maxAlleleVal = 2; + unsigned char alleleA = char(0); + unsigned char alleleB = char(1); + unsigned char alleleC = char(2); + // Create genotypes + auto genotypeAA = createTestNeutralGenotype(genomeSz, true, alleleA, alleleA); + auto genotypeAB = createTestNeutralGenotype(genomeSz, true, alleleA, alleleB); + auto genotypeAC = createTestNeutralGenotype(genomeSz, true, alleleA, alleleC); + auto genotypeBB = createTestNeutralGenotype(genomeSz, true, alleleB, alleleB); + auto genotypeBC = createTestNeutralGenotype(genomeSz, true, alleleB, alleleC); + auto genotypeCC = createTestNeutralGenotype(genomeSz, true, alleleC, alleleC); + vector>>> genotypeList = { + { // Genotypes of population 1 + genotypeAA, genotypeAA, genotypeAB, genotypeAB, genotypeAB, + genotypeAB, genotypeAC, genotypeAC, genotypeBB, genotypeCC + }, + { // Genotypes of population 2 + genotypeAA, genotypeAB, genotypeAC, genotypeBB, genotypeBC, + genotypeBC, genotypeBC, genotypeBC, genotypeCC, genotypeCC + } + }; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < patches.size(); p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pInd->overrideGenotype(NEUTRAL, genotypeList[p][i]); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop * patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + const double expectedFst = 0.0583; // calculated by hand from Weir and Cockerham 1984 + double calcError = abs(pNeutralStatistics->getFstWC() - expectedFst); + assert(calcError < 0.0001); + } + + // The Fst is a haploid sample equals that of a diploid sample if: + // 1 - allelic frequencies are equal + // 2 - the diploid sample has no heterozygotes + // (reference diploid Fst has already been calculated above) + { + const bool isDiploid{ false }; + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 10; + // Genetic setup + const int genomeSz = 1; + const set genePositions = { 0 }; + const float maxAlleleVal = 1; + unsigned char alleleValPopA = char(0); + unsigned char alleleValPopB = char(1); + const auto genotypeA = createTestNeutralGenotype(genomeSz, true, alleleValPopA); + const auto genotypeB = createTestNeutralGenotype(genomeSz, true, alleleValPopB); + vector>> genotypes = { + // 8 A, 2B (no heterozygotes) + genotypeA, genotypeA, genotypeA, genotypeA, genotypeB, + genotypeA, genotypeA, genotypeA, genotypeA, genotypeB + }; + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultHaploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < patches.size(); p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pInd->overrideGenotype(NEUTRAL, genotypes[i]); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop * patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getFstWC() == refWeirCockerhamDiploidFst); + } + + // Multi-locus case + // First locus has no variation between individuals, all heterozygotes (Fst = 0) + // Second locus has different fixed alleles between both populations (Fst = 1) + { + // Patch setup + const int nbPatches = 2; + const int nbIndsPerPop = 10; + // Genetic setup + const int genomeSz = 3; + const bool isDiploid{ true }; + const set genePositions = { 0, 2 }; // position 1 is arbitrarily empty + const float maxAlleleVal = 1; + unsigned char alleleValPopA = char(0); + unsigned char alleleValPopB = char(1); + map> genotypePop1; + map> genotypePop2; + // First locus: all heterozygotes, same genotype + genotypePop1.emplace(0, vector{alleleValPopA, alleleValPopB}); + genotypePop2.emplace(0, vector{alleleValPopA, alleleValPopB}); + // Second locus: different fixed alleles + genotypePop1.emplace(2, vector{alleleValPopA, alleleValPopA}); + genotypePop2.emplace(2, vector{alleleValPopB, alleleValPopB}); + + // Create two-patches landscape + Landscape* pLandscape = new Landscape; + vector patches(nbPatches); + vector cells(nbPatches); + set patchList; + for (int i = 0; i < nbPatches; i++) { + patches[i] = pLandscape->newPatch(i); + cells[i] = new Cell(0, 0, patches[i], 0); + patches[i]->addCell(cells[i], 0, 0); + patchList.insert(patches[i]->getPatchNum()); + } + const string indSampling = "all"; + const set stgToSample = { 1 }; + + // Create species trait structure + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + patchList, + indSampling, + stgToSample, + 1 + ); + const int nbLoci = genePositions.size(); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise populations + const int indStg = 1; + for (int p = 0; p < patches.size(); p++) { + Population* pPop = new Population(pSpecies, patches[p], 0, 1); + // create individuals and add to pop + for (int i = 0; i < nbIndsPerPop; i++) { + Individual* pInd = new Individual(pSpecies, cells[p], patches[p], indStg, 0, 0, 0.0, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + pInd->overrideGenotype(NEUTRAL, p == 0 ? genotypePop1 : genotypePop2); + pPop->recruit(pInd); + } + pPop->sampleIndsWithoutReplacement(indSampling, { indStg }); + } + + // Compute F-stats + auto pNeutralStatistics = make_unique(nbPatches, nbLoci); + pNeutralStatistics->updateAllNeutralTables( + pSpecies, + pLandscape, + patchList + ); + const int maxNbNeutralAlleles = static_cast(maxAlleleVal) + 1; + pNeutralStatistics->calculateFstatWC( + patchList, + nbIndsPerPop * patchList.size(), + nbLoci, + maxNbNeutralAlleles, + pSpecies, + pLandscape, + false + ); + assert(pNeutralStatistics->getPerLocusFst(0) == 0.0); + assert(pNeutralStatistics->getPerLocusFst(1) == 1.0); + } +} + +#endif // UNIT_TESTS diff --git a/unit_tests/testPopulation.cpp b/unit_tests/testPopulation.cpp new file mode 100644 index 0000000..c8dda07 --- /dev/null +++ b/unit_tests/testPopulation.cpp @@ -0,0 +1,307 @@ +#ifdef UNIT_TESTS + +#include "../Individual.h" +#include "../Population.h" + +void testPopulation() +{ + // Given a genetic load trait, offspring + // Survival is (inversely) proportional to the mutation rate + { + vector mutationRates = { 0.0, 0.1, 0.2 }; + vector survivingInds; + const int initialNbInds = 1000; + const float localK = 10000; // not limiting + vector localScaling = {1.0}; + + // Simple genetic layout + const bool isDiploid{ false }; // haploid suffices + const int genomeSz = 1; + const set genePositions = { 0 }; + + // All mutations are dominant lethal + const map mutParams{ + pair{GenParamType::MIN, 1}, + pair{GenParamType::MAX, 1} + }; + const map domParams{ + pair{GenParamType::MIN, 1}, + pair{GenParamType::MAX, 1} + }; + + for (float mutationRate : mutationRates) { + Landscape* pLandscape = new Landscape; + Patch* pPatch = pLandscape->newPatch(1); + Cell* pCell = new Cell(0, 0, pPatch, 0); + pPatch->addCell(pCell, 0, 0); + + Species* pSpecies = createDefaultSpecies(); + pSpecies->setDemogr(createDefaultHaploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + { }, "none", { }, 0 // no sampling + ); + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::GENETIC_LOAD, + sex_t::NA, + genePositions, + ExpressionType::MULTIPLICATIVE, + genePositions, // initial positions (all) + DistributionType::NONE, map{}, + DistributionType::UNIFORM, domParams, + true, // isInherited + mutationRate, // mutation rate + DistributionType::UNIFORM, mutParams, + DistributionType::UNIFORM, domParams, + isDiploid ? 2 : 1, + false + ); + pSpecies->addTrait(TraitType::GENETIC_LOAD, *spTr); + + Population pop = Population(pSpecies, pPatch, initialNbInds, 1); + pop.reproduction(localK, 1, 1, localScaling); // juveniles are checked for viability at birth + pop.fledge(); // non-overlapping: adults are replaced with juveniles + survivingInds.push_back(pop.getNbInds()); + } + assert(survivingInds[0] > survivingInds[1] + && survivingInds[1] > survivingInds[2]); + } + + // Dispersal is proportional to the mutation rate + { + vector mutationRates = { 0.0, 0.05, 0.1 }; + vector emigratingInds; + const int initialNbInds = 1000; + const float localK = 10000; // not limiting + vector localScaling = {1.0}; + + // Simple genetic layout + const bool isDiploid{ false }; // haploid suffices + const int genomeSz = 1; + const set genePositions = { 0 }; + + // Wild-types never emigrate, mutants always do + const map initParams{ + pair{GenParamType::MIN, 0}, + pair{GenParamType::MAX, 0} + }; + const map mutParams{ + pair{GenParamType::MIN, 1}, + pair{GenParamType::MAX, 1} + }; + + for (float mutationRate : mutationRates) { + Landscape* pLandscape = new Landscape; + Patch* pPatch = pLandscape->newPatch(1); + Cell* pCell = new Cell(0, 0, pPatch, 0); + pPatch->addCell(pCell, 0, 0); + + Species* pSpecies = createDefaultSpecies(); + emigRules emig; + emig.indVar = true; + emig.sexDep = false; + emig.densDep = false; + emig.stgDep = false; + pSpecies->setEmigRules(emig); + stageParams stg; + stg.nStages = 2; + pSpecies->setStage(stg); + pSpecies->setDemogr(createDefaultHaploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + { }, "none", { }, 0 // no sampling + ); + SpeciesTrait* spTr = new SpeciesTrait( + TraitType::E_D0, + sex_t::NA, + genePositions, + ExpressionType::ADDITIVE, + genePositions, // initial positions (all) + DistributionType::UNIFORM, initParams, // initial distribution and params + DistributionType::NONE, map{}, // initial dominance (none) + true, // isInherited + mutationRate, // mutation rate + DistributionType::UNIFORM, mutParams, // mutation dist and params + DistributionType::NONE, map{}, // no dominance + isDiploid ? 2 : 1, + false + ); + pSpecies->addTrait(TraitType::E_D0, *spTr); + + Population pop = Population(pSpecies, pPatch, initialNbInds, 1); + pop.reproduction(localK, 1, 1, localScaling); + pop.fledge(); // replace initial pop with juveniles + pop.emigration(localK); // select and flag emigrants + int popSize = pop.getNbInds(); + for (int i = 0; i < popSize; i++) { + pop.extractDisperser(i); // rm emigrants from pop + } + pop.clean(); + int nbEmigrating = popSize - pop.getNbInds(); // diff is nb of emigrants + if (mutationRate == 0.0) + assert(nbEmigrating == 0); + emigratingInds.push_back(nbEmigrating); + } + assert(emigratingInds[0] < emigratingInds[1] && emigratingInds[1] < emigratingInds[2]); + } + + // In the absence of selection, drift is solely responsible + // for changes in allele frequencies + { + const float tolerance = 0.02; + const float hetzTolerance = 0.05; + + float mutationRate = 0.0; + const float localK = 10000.0; + vector localScaling = {1.0}; + const int initialNbInds = localK; + const float initFreqA = 0.30; + const float exptdFreqHeteroZ = 2 * initFreqA * (1 - initFreqA); // according to HW + const int nbGens = 50; + float obsFreqA = initFreqA; + float obsFreqHeteroZ; + int nbInds = static_cast(localK); + + // Simple genetic layout + // 1 locus with two alleles A and B + const bool isDiploid{ true }; + const int genomeSz = 1; + const set genePositions = { 0 }; + const float maxAlleleVal = 1; + unsigned char alleleA = char(0); + unsigned char alleleB = char(1); + auto genotypeAA = createTestNeutralGenotype(genomeSz, true, alleleA, alleleA); + auto genotypeBB = createTestNeutralGenotype(genomeSz, true, alleleB, alleleB); + + // Landscape is a single cell + Landscape* pLandscape = new Landscape; + Patch* pPatch = pLandscape->newPatch(1); + Cell* pCell = new Cell(0, 0, pPatch, 0); + pPatch->addCell(pCell, 0, 0); + + Species* pSpecies = new Species(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + { }, "none", { }, 0 // no sampling + ); + SpeciesTrait* spTr = createTestNeutralSpTrait(maxAlleleVal, genePositions, isDiploid); + pSpecies->addTrait(TraitType::NEUTRAL, *spTr); + + // Initialise population with initial frequencies for AA and BB + Population pop = Population(pSpecies, pPatch, 0, 1); + for (int i = 0; i < initialNbInds; i++) { + Individual* pInd = new Individual(pSpecies, pCell, pPatch, 1, 0, 0, 0.5, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + if (i < initialNbInds * initFreqA) + pInd->overrideGenotype(NEUTRAL, genotypeAA); + else + pInd->overrideGenotype(NEUTRAL, genotypeBB); + pop.recruit(pInd); + } + + // Check allele frequencies conform to expectation through generations + float prevGenFreqA; + for (int yr = 0; yr < nbGens; yr++) { + pop.reproduction(localK, 1, 1, localScaling); + pop.fledge(); // replace initial pop with juveniles + pop.survival0(localK, 0, 0, localScaling); // flag juveniles for development + pop.survival1(); // develop to stage 1 (breeders) + pop.shuffleInds(); + + // Calculate expected allele frequency change from + // drift and previous generation frequencies + prevGenFreqA = obsFreqA; + float exptdChg = 2 * sqrt(prevGenFreqA * (1 - prevGenFreqA) / 2 * nbInds); + // ^ eq. 6.1 in Conservation and the Genomics of Populations, Allendorf et al. + // 95% CI for the allele frequency change + + // Check allele frequency change match equation + pop.sampleIndsWithoutReplacement("all", { 1 }); + pop.updatePopNeutralTables(); + obsFreqA = pop.getAlleleFrequency(0, alleleA); + float freqChg = abs(prevGenFreqA - obsFreqA); + assert(abs(freqChg) < tolerance); + + // If population is very large, heterozygosity should be roughly constant, + // i.e. inbreeding is negligible + nbInds = pop.getNbInds(); + // couldn't find a source for exptd change in heterozygosity so + // we assume Hardy-Weinberg equilibrium + obsFreqHeteroZ = static_cast(pop.getHeteroTally(0, alleleA)) / nbInds; + assert(abs(obsFreqHeteroZ - exptdFreqHeteroZ) < hetzTolerance); + } + } + + // Genetic load meets Hardy-Weinberg expectation on first generation + // If a lethal (s = 1) recessive (h = 0) allele starts at freq 0.6, + // then (if no mutations) next gen should have 0.6^2 = 0.36 homozygotes dying at birth + { + const float tolerance = 0.05; // high tolerance, still a lot of stochasticity + + const float initFreqA = 0.23; + const float sA = 1.0; // lethal + const float hA = 0.0; // fully recessive + const float sB = 0.0; // benign + const float hB = 1.0; // fully dominant + float mutationRate = 0.0; + const float localK = 10000.0; + vector localScaling = {1.0}; + + const int initialNbInds = localK; + const float expectedFreqAA = initFreqA * initFreqA; + + // Simple genetic layout + const bool isDiploid{ true }; // HW only applies to diploids + const int genomeSz = 1; + const set genePositions = { 0 }; + const float maxAlleleVal = 1; + unsigned char alleleA = char(0); + unsigned char alleleB = char(1); + auto genotypeAA = createTestGenotype(genomeSz, true, sA, sA, hA, hA); + auto genotypeBB = createTestGenotype(genomeSz, true, sB, sB, hB, hB); + + Landscape* pLandscape = new Landscape; + Patch* pPatch = pLandscape->newPatch(1); + Cell* pCell = new Cell(0, 0, pPatch, 0); + pPatch->addCell(pCell, 0, 0); + + Species* pSpecies = new Species(); + pSpecies->setDemogr(createDefaultDiploidDemogrParams()); + pSpecies->setGeneticParameters( + set{genomeSz - 1}, // single chromosome + genomeSz, + 0.0, // no recombination + { }, "none", { }, 0 // no sampling + ); + SpeciesTrait* spTr = createTestGenLoadTrait(genePositions, isDiploid); + pSpecies->addTrait(TraitType::GENETIC_LOAD, *spTr); + + // Initialise population with + Population pop = Population(pSpecies, pPatch, 0, 1); + for (int i = 0; i < initialNbInds; i++) { + Individual* pInd = new Individual(pSpecies, pCell, pPatch, 1, 0, 0, 0.5, false, 1); + pInd->setUpGenes(pSpecies, 1.0); + if (i < initialNbInds * initFreqA) + pInd->overrideGenotype(GENETIC_LOAD1, genotypeAA); + else + pInd->overrideGenotype(GENETIC_LOAD1, genotypeBB); + pop.recruit(pInd); + } + + // Check allele frequencies conform to HW + pop.shuffleInds(); + pop.reproduction(localK, 1, 1, localScaling); + pop.fledge(); // replace initial pop with juveniles + double obsFreqUnviable = 1 - pop.getNbInds() / localK; + assert(abs(obsFreqUnviable - expectedFreqAA) < tolerance); + } +} + +#endif // UNIT_TESTS