#include "spawner.h"
#include "errorhandler.h"
#include "keeper.h"
#include "areatime.h"
#include "readfunc.h"
#include "mathfunc.h"
#include "readword.h"
#include "readaggregation.h"
#include "gadget.h"
#include "global.h"
SpawnData::SpawnData(CommentStream& infile, int maxage, const LengthGroupDivision* const lgrpdiv,
const IntVector& Areas, const AreaClass* const Area, const char* givenname,
const TimeClass* const TimeInfo, Keeper* const keeper) : HasName(givenname), LivesOnAreas(Areas) {
keeper->addString("spawner");
int i, tmpint = 0;
ratioscale = 1.0; //JMB used to scale the ratios to ensure that they sum to 1
char text[MaxStrLength];
strncpy(text, "", MaxStrLength);
ifstream datafile;
CommentStream subdata(datafile);
functionname = new char[MaxStrLength];
strncpy(functionname, "", MaxStrLength);
spawnLgrpDiv = 0;
LgrpDiv = new LengthGroupDivision(*lgrpdiv);
if (LgrpDiv->Error())
handle.logMessage(LOGFAIL, "Error in spawner - failed to create length group");
int numlength = LgrpDiv->numLengthGroups();
spawnFirstYear = TimeInfo->getFirstYear();
spawnLastYear = TimeInfo->getLastYear();
spawnProportion.resize(numlength, 0.0);
spawnMortality.resize(numlength, 0.0);
spawnWeightLoss.resize(numlength, 0.0);
infile >> text >> ws;
if ((strcasecmp(text, "spawnstep") != 0) && (strcasecmp(text, "spawnsteps") != 0))
handle.logFileUnexpected(LOGFAIL, "spawnsteps", text);
while (isdigit(infile.peek()) && !infile.eof()) {
infile >> tmpint >> ws;
spawnStep.resize(1, tmpint);
}
for (i = 0; i < spawnStep.Size(); i++)
if (spawnStep[i] < 1 || spawnStep[i] > TimeInfo->numSteps())
handle.logFileMessage(LOGFAIL, "invalid spawning step", spawnStep[i]);
infile >> text >> ws;
if ((strcasecmp(text, "spawnarea") != 0) && (strcasecmp(text, "spawnareas") != 0))
handle.logFileUnexpected(LOGFAIL, "spawnareas", text);
while (isdigit(infile.peek()) && !infile.eof()) {
infile >> tmpint >> ws;
spawnArea.resize(1, tmpint);
}
for (i = 0; i < spawnArea.Size(); i++)
spawnArea[i] = Area->getInnerArea(spawnArea[i]);
infile >> text >> ws;
//JMB check for optional firstspwanyear and lastspawnyear values
if (strcasecmp(text, "firstspawnyear") == 0) {
infile >> spawnFirstYear >> text >> ws;
if (spawnFirstYear < TimeInfo->getFirstYear())
handle.logFileMessage(LOGFAIL, "invalid first spawning year", spawnFirstYear);
}
if (strcasecmp(text, "lastspawnyear") == 0) {
infile >> spawnLastYear >> text >> ws;
if (spawnLastYear > TimeInfo->getLastYear())
handle.logFileMessage(LOGFAIL, "invalid last spawning year", spawnLastYear);
}
if (strcasecmp(text, "spawnstocksandratios") == 0) {
onlyParent = 0;
i = 0;
infile >> text >> ws;
while (strcasecmp(text, "proportionfunction") != 0 && !infile.eof()) {
spawnStockNames.resize(new char[strlen(text) + 1]);
strcpy(spawnStockNames[i], text);
spawnRatio.resize(1, keeper);
if (!(infile >> spawnRatio[i]))
handle.logFileMessage(LOGFAIL, "invalid format for spawn ratio");
spawnRatio[i].Inform(keeper);
infile >> text >> ws;
i++;
}
} else if (strcasecmp(text, "onlyparent") == 0) {
onlyParent = 1;
infile >> text >> ws;
} else
handle.logFileUnexpected(LOGFAIL, "spawnstocksandratios or onlyparent", text);
if (infile.eof())
handle.logFileEOFMessage(LOGFAIL);
if (strcasecmp(text, "proportionfunction") != 0)
handle.logFileUnexpected(LOGFAIL, "proportionfunction", text);
infile >> text >> ws;
if (strcasecmp(text, "constant") == 0)
fnProportion = new ConstSelectFunc();
else if (strcasecmp(text, "straightline") == 0)
fnProportion = new StraightSelectFunc();
else if (strcasecmp(text, "exponential") == 0)
fnProportion = new ExpSelectFunc();
else
handle.logFileMessage(LOGFAIL, "unrecognised proportion function", text);
fnProportion->readConstants(infile, TimeInfo, keeper);
readWordAndValue(infile, "mortalityfunction", text);
if (strcasecmp(text, "constant") == 0)
fnMortality = new ConstSelectFunc();
else if (strcasecmp(text, "straightline") == 0)
fnMortality = new StraightSelectFunc();
else if (strcasecmp(text, "exponential") == 0)
fnMortality = new ExpSelectFunc();
else
handle.logFileMessage(LOGFAIL, "unrecognised mortality function", text);
fnMortality->readConstants(infile, TimeInfo, keeper);
readWordAndValue(infile, "weightlossfunction", text);
if (strcasecmp(text, "constant") == 0)
fnWeightLoss = new ConstSelectFunc();
else if (strcasecmp(text, "straightline") == 0)
fnWeightLoss = new StraightSelectFunc();
else if (strcasecmp(text, "exponential") == 0)
fnWeightLoss = new ExpSelectFunc();
else
handle.logFileMessage(LOGFAIL, "unrecognised weight loss function", text);
fnWeightLoss->readConstants(infile, TimeInfo, keeper);
if (!onlyParent) {
infile >> text >> ws;
if (strcasecmp(text, "recruitment") != 0)
handle.logFileUnexpected(LOGFAIL, "recruitment", text);
//read in the recruitment function details
functionnumber = 0;
infile >> functionname >> ws;
if (strcasecmp(functionname, "simplessb") == 0) {
functionnumber = 1;
spawnParameters.resize(1, keeper);
} else if (strcasecmp(functionname, "ricker") == 0) {
functionnumber = 2;
spawnParameters.resize(2, keeper);
} else if (strcasecmp(functionname, "bevertonholt") == 0) {
functionnumber = 3;
spawnParameters.resize(2, keeper);
} else if (strcasecmp(functionname, "fecundity") == 0) {
functionnumber = 4;
spawnParameters.resize(5, keeper);
} else if (strcasecmp(functionname, "baleen") == 0) {
functionnumber = 5;
spawnParameters.resize(4, keeper);
} else if (strcasecmp(functionname, "hockeystick") == 0) {
functionnumber = 6;
spawnParameters.resize(2, keeper);
} else
handle.logFileMessage(LOGFAIL, "unrecognised recruitment function", functionname);
spawnParameters.read(infile, TimeInfo, keeper);
//read in the details about the new stock
stockParameters.resize(4, keeper);
infile >> text >> ws;
if (strcasecmp(text, "stockparameters") != 0)
handle.logFileUnexpected(LOGFAIL, "stockparameters", text);
stockParameters.read(infile, TimeInfo, keeper);
//resize spawnNumbers to store details of the spawning stock biomass
for (i = 0; i < areas.Size(); i++)
spawnNumbers.resize(new DoubleMatrix(maxage + 1, numlength, 0.0));
}
infile >> ws;
if (!infile.eof()) {
infile >> text >> ws;
handle.logFileUnexpected(LOGFAIL, "<end of file>", text);
}
handle.logMessage(LOGMESSAGE, "Read spawning data file");
keeper->clearLast();
}
SpawnData::~SpawnData() {
int i;
for (i = 0; i < spawnStockNames.Size(); i++)
delete[] spawnStockNames[i];
for (i = 0; i < spawnNumbers.Size(); i++)
delete spawnNumbers[i];
for (i = 0; i < CI.Size(); i++)
delete CI[i];
delete LgrpDiv;
delete spawnLgrpDiv;
delete fnProportion;
delete fnMortality;
delete fnWeightLoss;
delete[] functionname;
}
void SpawnData::setStock(StockPtrVector& stockvec) {
int i, j, index;
if (onlyParent)
return;
for (i = 0; i < stockvec.Size(); i++)
for (j = 0; j < spawnStockNames.Size(); j++)
if (strcasecmp(stockvec[i]->getName(), spawnStockNames[j]) == 0)
spawnStocks.resize(stockvec[i]);
if (spawnStocks.Size() != spawnStockNames.Size()) {
handle.logMessage(LOGWARN, "Error in spawner - failed to match spawning stocks");
for (i = 0; i < stockvec.Size(); i++)
handle.logMessage(LOGWARN, "Error in spawner - found stock", stockvec[i]->getName());
for (i = 0; i < spawnStockNames.Size(); i++)
handle.logMessage(LOGWARN, "Error in spawner - looking for stock", spawnStockNames[i]);
handle.logMessage(LOGFAIL, ""); //JMB this will exit gadget
}
//JMB ensure that the ratio vector is indexed in the right order
ratioindex.resize(spawnStocks.Size(), 0);
for (i = 0; i < spawnStocks.Size(); i++)
for (j = 0; j < spawnStockNames.Size(); j++)
if (strcasecmp(spawnStocks[i]->getName(), spawnStockNames[j]) == 0)
ratioindex[i] = j;
//JMB check that the spawned stocks are defined on all the areas
spawnAge = 9999;
double minlength = 9999.0;
double maxlength = 0.0;
double dl = 9999.0;
for (i = 0; i < spawnStocks.Size(); i++) {
index = 0;
for (j = 0; j < spawnArea.Size(); j++)
if (!spawnStocks[i]->isInArea(spawnArea[j]))
index++;
if (index != 0)
handle.logMessage(LOGWARN, "Warning in spawner - spawned stock isnt defined on all areas");
spawnAge = min(spawnStocks[i]->minAge(), spawnAge);
minlength = min(spawnStocks[i]->getLengthGroupDiv()->minLength(), minlength);
maxlength = max(spawnStocks[i]->getLengthGroupDiv()->maxLength(), maxlength);
dl = min(spawnStocks[i]->getLengthGroupDiv()->dl(), dl);
}
spawnLgrpDiv = new LengthGroupDivision(minlength, maxlength, dl);
if (spawnLgrpDiv->Error())
handle.logMessage(LOGFAIL, "Error in spawner - failed to create length group");
for (i = 0; i < spawnStocks.Size(); i++) {
CI.resize(new ConversionIndex(spawnLgrpDiv, spawnStocks[i]->getLengthGroupDiv()));
if (CI[i]->Error())
handle.logMessage(LOGFAIL, "Error in spawner - error when checking length structure");
}
IntVector minlv(1, 0);
IntVector sizev(1, spawnLgrpDiv->numLengthGroups());
Storage.resize(areas.Size(), spawnAge, minlv, sizev);
for (i = 0; i < Storage.Size(); i++)
Storage[i].setToZero();
}
void SpawnData::Spawn(AgeBandMatrix& Alkeys, int area, const TimeClass* const TimeInfo) {
if (!onlyParent)
spawnParameters.Update(TimeInfo);
int age, len;
int inarea = this->areaNum(area);
PopInfo pop;
for (age = Alkeys.minAge(); age <= Alkeys.maxAge(); age++) {
//subtract mortality and reduce the weight of the living ones.
for (len = Alkeys.minLength(age); len < Alkeys.maxLength(age); len++) {
if (!isZero(spawnProportion[len])) {
pop = Alkeys[age][len] * spawnProportion[len];
//calculate the spawning stock biomass if needed
if (!onlyParent)
(*spawnNumbers[inarea])[age][len] = calcSpawnNumber(age, len, pop.N, pop.W);
pop *= exp(-spawnMortality[len]);
pop.W -= (spawnWeightLoss[len] * pop.W);
Alkeys[age][len] *= (1.0 - spawnProportion[len]);
Alkeys[age][len] += pop;
}
}
}
}
void SpawnData::addSpawnStock(int area, const TimeClass* const TimeInfo) {
if (onlyParent)
return;
int s, age, len;
int inarea = this->areaNum(area);
double tmp, length, N, total, sum;
//create a length distribution and mean weight for the new stock
stockParameters.Update(TimeInfo);
if (handle.getLogLevel() >= LOGWARN) {
if (isZero(stockParameters[1]))
handle.logMessage(LOGWARN, "Warning in spawner - invalid standard deviation for spawned stock", this->getName());
if (stockParameters[0] < spawnLgrpDiv->minLength())
handle.logMessage(LOGWARN, "Warning in spawner - mean length is less than minimum length for stock", this->getName());
if (stockParameters[0] > spawnLgrpDiv->maxLength())
handle.logMessage(LOGWARN, "Warning in spawner - mean length is greater than maximum length for stock", this->getName());
}
sum = 0.0;
Storage[inarea].setToZero();
if (stockParameters[1] > verysmall) {
tmp = 1.0 / (2 * stockParameters[1] * stockParameters[1]);
for (len = 0; len < spawnLgrpDiv->numLengthGroups(); len++) {
length = spawnLgrpDiv->meanLength(len) - stockParameters[0];
N = exp(-(length * length * tmp));
Storage[inarea][spawnAge][len].N = N;
sum += N;
}
}
//calculate the total number of recruits and distribute this through the length groups
if (!isZero(sum)) {
tmp = 0.0; //JMB dummy temperature of zero
total = calcRecruitNumber(tmp, inarea) / sum;
for (len = 0; len < spawnLgrpDiv->numLengthGroups(); len++) {
length = spawnLgrpDiv->meanLength(len);
Storage[inarea][spawnAge][len].N *= total;
Storage[inarea][spawnAge][len].W = stockParameters[2] * pow(length, stockParameters[3]);
}
//add this to the spawned stocks
for (s = 0; s < spawnStocks.Size(); s++) {
if (!spawnStocks[s]->isInArea(area))
handle.logMessage(LOGFAIL, "Error in spawner - spawned stock doesnt live on area", area);
tmp = spawnRatio[ratioindex[s]] * ratioscale;
spawnStocks[s]->Add(Storage[inarea], CI[s], area, tmp);
}
}
}
int SpawnData::isSpawnStepArea(int area, const TimeClass* const TimeInfo) {
int i, j;
if ((TimeInfo->getYear() < spawnFirstYear) || (TimeInfo->getYear() > spawnLastYear))
return 0;
for (i = 0; i < spawnStep.Size(); i++)
for (j = 0; j < spawnArea.Size(); j++)
if ((spawnStep[i] == TimeInfo->getStep()) && (spawnArea[j] == area))
return 1;
return 0;
}
void SpawnData::Reset(const TimeClass* const TimeInfo) {
int i;
fnProportion->updateConstants(TimeInfo);
if (fnProportion->didChange(TimeInfo)) {
for (i = 0; i < LgrpDiv->numLengthGroups(); i++) {
spawnProportion[i] = fnProportion->calculate(LgrpDiv->meanLength(i));
if (spawnProportion[i] < 0.0) {
handle.logMessage(LOGWARN, "Warning in spawner - function outside bounds", spawnProportion[i]);
spawnProportion[i] = 0.0;
}
if (spawnProportion[i] > 1.0) {
handle.logMessage(LOGWARN, "Warning in spawner - function outside bounds", spawnProportion[i]);
spawnProportion[i] = 1.0;
}
}
}
fnWeightLoss->updateConstants(TimeInfo);
if (fnWeightLoss->didChange(TimeInfo)) {
for (i = 0; i < LgrpDiv->numLengthGroups(); i++) {
spawnWeightLoss[i] = fnWeightLoss->calculate(LgrpDiv->meanLength(i));
if (spawnWeightLoss[i] < 0.0) {
handle.logMessage(LOGWARN, "Warning in spawner - function outside bounds", spawnWeightLoss[i]);
spawnWeightLoss[i] = 0.0;
}
if (spawnWeightLoss[i] > 1.0) {
handle.logMessage(LOGWARN, "Warning in spawner - function outside bounds", spawnWeightLoss[i]);
spawnWeightLoss[i] = 1.0;
}
}
}
fnMortality->updateConstants(TimeInfo);
if (fnMortality->didChange(TimeInfo)) {
for (i = 0; i < LgrpDiv->numLengthGroups(); i++) {
spawnMortality[i] = fnMortality->calculate(LgrpDiv->meanLength(i));
}
}
//JMB check that the sum of the ratios is 1
if ((!onlyParent) && (TimeInfo->getTime() == 1)) {
ratioscale = 0.0;
for (i = 0; i < spawnRatio.Size(); i++ )
ratioscale += spawnRatio[i];
if (isZero(ratioscale)) {
handle.logMessage(LOGWARN, "Warning in spawner - specified ratios are zero");
ratioscale = 1.0;
} else if (isEqual(ratioscale, 1.0)) {
// do nothing
} else {
handle.logMessage(LOGWARN, "Warning in spawner - scaling ratios using", ratioscale);
ratioscale = 1.0 / ratioscale;
}
}
if (handle.getLogLevel() >= LOGMESSAGE)
handle.logMessage(LOGMESSAGE, "Reset spawning data for stock", this->getName());
}
double SpawnData::calcSpawnNumber(int age, int len, double number, double weight) {
double temp = 0.0;
switch (functionnumber) {
case 1:
case 2:
case 3:
temp = number * weight;
break;
case 4:
temp = pow(LgrpDiv->meanLength(len), spawnParameters[1]) * pow(age, spawnParameters[2])
* pow(number, spawnParameters[3]) * pow(weight, spawnParameters[4]);
break;
case 5:
temp = number;
break;
default:
handle.logMessage(LOGWARN, "Warning in spawner - unrecognised recruitment function", functionname);
break;
}
return temp;
}
double SpawnData::calcRecruitNumber(double temp, int inarea) {
int age, len;
double total = 0.0, ssb = 0.0;
for (age = 0; age < (*spawnNumbers[inarea]).Nrow(); age++)
for (len = 0; len < (*spawnNumbers[inarea]).Ncol(age); len++)
ssb += (*spawnNumbers[inarea])[age][len];
switch (functionnumber) {
case 1:
case 4:
total = ssb * spawnParameters[0];
break;
case 2:
total = ssb * spawnParameters[0] * exp(-spawnParameters[1] * ssb);
break;
case 3:
total = ssb * spawnParameters[0] / (spawnParameters[1] + ssb);
break;
case 5:
total = ssb * spawnParameters[0] *
max(1 + spawnParameters[1] * (1- pow(ssb / spawnParameters[2],double(spawnParameters[3]))),0.0);
break;
default:
handle.logMessage(LOGWARN, "Warning in spawner - unrecognised recruitment function", functionname);
break;
}
return total;
}
void SpawnData::Print(ofstream& outfile) const {
if (onlyParent) {
outfile << "\nSpawning data\n\t** Only modelling the affect on the parent stock **"
<< "\n\t** No recruits are created during the spawning process **\n";
return;
}
int i;
outfile << "\nSpawning data\n\tNames of spawned stocks:";
for (i = 0; i < spawnStockNames.Size(); i++)
outfile << sep << spawnStockNames[i];
outfile << "\n\tRatio spawning into each stock:";
for (i = 0; i < spawnRatio.Size(); i++)
outfile << sep << (spawnRatio[ratioindex[i]] * ratioscale);
outfile << "\n\t";
spawnLgrpDiv->Print(outfile);
outfile << "\tStored numbers:\n";
for (i = 0; i < areas.Size(); i++) {
outfile << "\tInternal area " << areas[i] << "\n\tNumbers\n";
Storage[i].printNumbers(outfile);
outfile << "\tMean weights\n";
Storage[i].printWeights(outfile);
}
}