20 GA_params.maxpop = 100;
21 Ind.resize(GA_params.maxpop);
22 Ind_old.resize(GA_params.maxpop);
26 GA_params.cross_over_type = 1;
35 ifstream file(filename);
39 GA_params.RCGA =
false;
42 filenames.pathname = Model->OutputPath();
44 while (file.eof() ==
false)
46 s = aquiutils::getline(file);
48 {
if (s[0] ==
"maxpop") GA_params.maxpop = atoi(s[1].c_str());
49 if (s[0] ==
"ngen") GA_params.nGen = atoi(s[1].c_str());
50 if (s[0] ==
"pcross") GA_params.pcross = atof(s[1].c_str());
51 if (s[0] ==
"pmute") GA_params.pmute = atof(s[1].c_str());
52 if (s[0] ==
"shakescale") GA_params.shakescale = atof(s[1].c_str());
53 if (s[0] ==
"shakescalered") GA_params.shakescalered = atof(s[1].c_str());
54 if (s[0] ==
"outputfile") filenames.outputfilename = s[1];
55 if (s[0] ==
"getfromfilename") filenames.getfromfilename = s[1].c_str();
56 if (s[0] ==
"initial_population") filenames.initialpopfilemame = s[1];
57 if (s[0] ==
"numthreads") numberOfThreads = atoi(s[1].c_str());
62 for (
int i=0; i<Model->Parameters().size(); i++)
66 if (Model->Parameters()[i]->GetPriorDistribution() ==
"lognormal")
67 { minval.push_back(log10(Model->Parameters()[i]->GetRange(
_range::low)));
68 maxval.push_back(log10(Model->Parameters()[i]->GetRange(
_range::high)));
73 minval.push_back(Model->Parameters()[i]->GetRange(
_range::low));
74 maxval.push_back(Model->Parameters()[i]->GetRange(
_range::high));
76 apply_to_all.push_back(
false);
77 if (Model->Parameters()[i]->GetPriorDistribution() ==
"lognormal")
82 paramname.push_back(Model->Parameters().getKeyAtIndex(i));
87 Ind.resize(GA_params.maxpop);
88 Ind_old.resize(GA_params.maxpop);
91 GA_params.cross_over_type = 1;
93 for (
int i=0; i<GA_params.maxpop; i++)
96 Ind[i].fit_measures.resize(model->ObservationsCount()*3);
98 Ind_old[i].fit_measures.resize(model->ObservationsCount()*3);
101 for (
int i = 0; i<GA_params.nParam; i++)
102 Setminmax(i, minval[i], maxval[i],4);
111 GA_params.nParam = 0;
112 GA_params.pcross = 1;
114 GA_params.RCGA =
false;
115 numberOfThreads = 20;
117 filenames.pathname = Model->GetOutputPath();
118 GA_params.maxpop = max(1,GA_params.maxpop);
119 for (
unsigned int i=0; i<Model->Parameters().size(); i++)
124 { minval.push_back(log10(Model->parameter(i)->GetVal(
"low")));
125 maxval.push_back(log10(Model->parameter(i)->GetVal(
"high")));
130 minval.push_back(Model->parameter(i)->GetVal(
"low"));
131 maxval.push_back(Model->parameter(i)->GetVal(
"high"));
133 apply_to_all.push_back(
false);
139 paramname.push_back(Model->GetParameterName(i));
144 Ind.resize(GA_params.maxpop);
145 Ind_old.resize(GA_params.maxpop);
148 GA_params.cross_over_type = 1;
150 for (
int i=0; i<GA_params.maxpop; i++)
153 Ind[i].fit_measures.resize(model->ObservationsCount());
155 Ind_old[i].fit_measures.resize(model->ObservationsCount());
158 for (
int j = 0; j < GA_params.nParam; j++)
159 Setminmax(j, minval[j], maxval[j], 4);
167 GA_params.nParam = Model->Parameters().size();
169 for (
unsigned int i=0; i<Model->Parameters().size(); i++)
173 { minval.push_back(log10(Model->parameter(i)->GetVal(
"low")));
174 maxval.push_back(log10(Model->parameter(i)->GetVal(
"high")));
179 minval.push_back(Model->parameter(i)->GetVal(
"low"));
180 maxval.push_back(Model->parameter(i)->GetVal(
"high"));
182 apply_to_all.push_back(
false);
188 paramname.push_back(Model->GetParameterName(i));
193 Ind.resize(GA_params.maxpop);
194 Ind_old.resize(GA_params.maxpop);
197 GA_params.cross_over_type = 1;
199 for (
int i=0; i<GA_params.maxpop; i++)
202 Ind[i].fit_measures.resize(Model->ObservationsCount());
204 Ind_old[i].fit_measures.resize(Model->ObservationsCount());
207 for (
int j = 0; j < GA_params.nParam; j++)
208 Setminmax(j, minval[j], maxval[j], 4);
216 Ind.resize(GA_params.maxpop);
217 Ind_old.resize(GA_params.maxpop);
218 for (
int i=0; i<GA_params.maxpop; i++)
231 GA_params.maxpop = n;
235 Ind.resize(GA_params.maxpop);
236 Ind_old.resize(GA_params.maxpop);
237 for (
int i=0; i<n; i++)
241 for (
int j = 0; j<nParam; j++)
243 Ind[i].minrange[j] = TempInd.
minrange[j];
244 Ind[i].maxrange[j] = TempInd.
maxrange[j];
245 Ind[i].precision[j] = TempInd.
precision[j];
246 Ind_old[i].minrange[j] = TempInd.
minrange[j];
247 Ind_old[i].maxrange[j] = TempInd.
maxrange[j];
248 Ind_old[i].precision[j] = TempInd.
precision[j];
259 Ind.resize(GA_params.maxpop);
260 Ind_old.resize(GA_params.maxpop);
298 for (
int i=0; i<GA_params.maxpop; i++)
303 if (filenames.initialpopfilemame!=
"")
305 getinifromoutput(filenames.pathname+filenames.initialpopfilemame);
306 for (
int i=0; i<initial_pop.size(); i++)
307 for (
int j=0; j<max(
int(initial_pop[i].size()),GA_params.nParam); j++)
309 Ind[i].x[j] = log10(initial_pop[i][j]);
311 Ind[i].x[j] = initial_pop[i][j];
318 for (
int i=0; i<GA_params.maxpop; i++)
320 Ind[i].maxrange[a] = maxrange;
321 Ind[i].minrange[a] = minrange;
322 Ind[i].precision[a] = prec;
332 vector<vector<double>> inp;
334 inp.resize(GA_params.maxpop);
337 for (
int k=0; k<GA_params.maxpop; k++)
338 inp[k].resize(GA_params.nParam);
340 vector<double> time_(GA_params.maxpop);
341 vector<int> epochs(GA_params.maxpop);
345 for (
int k = 0; k < GA_params.maxpop; k++)
347 for (
int i = 0; i < GA_params.nParam; i++)
351 inp[k][i] = Ind[k].x[i];
355 inp[k][i] = pow(10, Ind[k].
x[i]);
359 Ind[k].actual_fitness = 0;
365 for (
int i = 0; i < GA_params.nParam; i++)
366 Models[k].SetParameterValue(i, inp[k][i]);
373 omp_set_num_threads(numberOfThreads);
376#pragma omp parallel for
377 for (
int k=0; k<GA_params.maxpop; k++)
380 if (GA_params.Steepest_Descent && k<min(GA_params.maxpop/10,1))
383 qDebug()<<
"Prior Likelihood: " <<Models[k].GetObjectiveFunctionValue();
384 CVector updated_params;
385 for (
int j=0; j<5; j++)
386 updated_params = Models[k].GradientUpdate();
387 for (
int i = 0; i < GA_params.nParam; i++)
391 inp[k][i] = updated_params[i];
392 Ind[k].x[i] = updated_params[i];
397 inp[k][i] = updated_params[i];
398 Ind[k].x[i] = log10(updated_params[i]);
403 qDebug()<<
"Posterior Likelihood: " <<Models[k].GetObjectiveFunctionValue();
410 FileOut = fopen((filenames.pathname+
"detail_GA.txt").c_str(),
"a");
411 fprintf(FileOut,
"%i, ", k);
412 for (
int l=0; l<Ind[0].nParams; l++)
414 fprintf(FileOut,
"%le, ", pow(10,Ind[k].
x[l]));
416 fprintf(FileOut,
"%le, ", Ind[k].
x[l]);
420 fprintf(FileOut,
"\n");
423 time_t t0 = time(
nullptr);
427 Ind[k].actual_fitness = Models[k].GetObjectiveFunctionValue();
432 time_[k] = time(
nullptr)-t0;
440 if (omp_get_thread_num() == 0)
457 Model_out = Models[maxfitness()];
466 assignfitness_rank(GA_params.N);
475 int a = maxfitness();
478 for (
int i=2; i<GA_params.maxpop; i+=2)
481 int j1 = fitdist.GetRand();
482 int j2 = fitdist.GetRand();
483 double x = fitdist.GetRndUniF(0,1);
484 if (
x<GA_params.pcross)
485 if (GA_params.cross_over_type == 1)
486 cross(Ind_old[j1], Ind_old[j2], Ind[i], Ind[min(i+1,GA_params.maxpop-1)]);
488 cross2p(Ind_old[j1], Ind_old[j2], Ind[i], Ind[min(i + 1, GA_params.maxpop - 1)]);
491 Ind[i] = Ind_old[j1];
492 Ind[i+1] = Ind_old[j2];
503 for (
int i=0; i<GA_params.maxpop; i++)
505 int a = maxfitness();
508 for (
int i=2; i<GA_params.maxpop; i+=2)
510 int j1 = fitdist.GetRand();
511 int j2 = fitdist.GetRand();
513 if (
x<GA_params.pcross)
514 cross_RC_L(Ind_old[j1], Ind_old[j2], Ind[i], Ind[i+1]);
517 Ind[i] = Ind_old[j1];
518 Ind[i+1] = Ind_old[j2];
526 if (aquiutils::tolower(varname) ==
"maxpop" || varname ==
"Population") {GA_params.maxpop = aquiutils::atoi(value); setnumpop(GA_params.maxpop);
return true;}
527 if (aquiutils::tolower(varname) ==
"ngen" || varname ==
"Number of Generations") {GA_params.nGen = aquiutils::atoi(value);
return true;}
528 if (aquiutils::tolower(varname) ==
"pcross" || varname ==
"Cross-over probability") {GA_params.pcross = aquiutils::atof(value);
return true;}
529 if (aquiutils::tolower(varname) ==
"pmute" || varname ==
"Mutation probability") {GA_params.pmute = aquiutils::atof(value);
return true;}
530 if (aquiutils::tolower(varname) ==
"shakescale" || varname ==
"Shake coefficient") {GA_params.shakescale = aquiutils::atof(value);
return true;}
531 if (aquiutils::tolower(varname) ==
"shakescalered" || varname ==
"Shake coefficient reduction factor") {GA_params.shakescalered = aquiutils::atof(value);
return true;}
532 if (aquiutils::tolower(varname) ==
"outputfile" || varname ==
"GA output file") {filenames.outputfilename = value;
return true;}
533 if (aquiutils::tolower(varname) ==
"getfromfilename") {filenames.getfromfilename = value.c_str();
return true;}
534 if (aquiutils::tolower(varname) ==
"initial_population") {filenames.initialpopfilemame = value;
return true;}
535 if (aquiutils::tolower(varname) ==
"numthreads" || varname ==
"Number of threads to be used") {numberOfThreads = aquiutils::atoi(value.c_str());
return true;}
536 if (aquiutils::tolower(varname) ==
"steepest descent") {
538 if (aquiutils::tolower(value) ==
"true")
539 GA_params.Steepest_Descent =
true;
541 GA_params.Steepest_Descent =
false;
544 last_error =
"Property '" + varname +
"' was not found!";
551 for (map<string,string>::const_iterator it=arguments.begin(); it!=arguments.end(); it++)
552 SetProperty(it->first, it->second);
560 for (
int i=0; i<GA_params.maxpop; i++)
561 sum += Ind[i].fitness;
562 return sum/GA_params.maxpop;
570 FileOut = fopen((filenames.pathname +
"detail_GA.txt").c_str(),
"a");
571 fprintf(FileOut,
"%s\n", s.c_str());
580 QCoreApplication::processEvents();
582 string RunFileName = filenames.pathname + filenames.outputfilename;
587 FileOut = fopen(RunFileName.c_str(),
"w");
589 FileOut1 = fopen((filenames.pathname +
"detail_GA.txt").c_str(),
"w");
592 double shakescaleini = GA_params.shakescale;
594 vector<double> X(Ind[0].nParams);
597 double ininumenhancements = GA_params.numenhancements;
598 GA_params.numenhancements = 0;
600 CMatrix Fitness(GA_params.nGen, 3);
603 Models.resize(GA_params.maxpop);
604 for (
int k=0; k<GA_params.maxpop; k++)
606 for (current_generation=0; current_generation<GA_params.nGen; current_generation++)
609 write_to_detailed_GA(
"Assigning fitnesses ...");
613 write_to_detailed_GA(
"Assigning fitnesses done!");
614 FileOut = fopen(RunFileName.c_str(),
"a");
615 printf(
"Generation: %i\n", current_generation);
616 fprintf(FileOut,
"Generation: %i\n", current_generation);
617 fprintf(FileOut,
"ID, ");
618 for (
int k=0; k<Ind[0].nParams; k++)
619 fprintf(FileOut,
"%s, ", paramname[k].c_str());
622 for (
unsigned int i=0; i<Model->ObservationsCount();i++)
624 fprintf(FileOut,
"%s, %s, %s", (Model->observation(i)->GetName()+
"_MSE").c_str(), (Model->observation(i)->GetName()+
"_R2").c_str(), (Model->observation(i)->GetName()+
"_NSE").c_str());
626 fprintf(FileOut,
"\n");
627 write_to_detailed_GA(
"Generation: " + aquiutils::numbertostring(current_generation));
628 for (
int j1=0; j1<GA_params.maxpop; j1++)
631 fprintf(FileOut,
"%i, ", j1);
633 for (
int k=0; k<Ind[0].nParams; k++)
635 fprintf(FileOut,
"%le, ", pow(10, Ind[j1].
x[k]));
637 fprintf(FileOut,
"%le, ", Ind[j1].
x[k]);
639 fprintf(FileOut,
"%le, %le, %i, ", Ind[j1].actual_fitness, Ind[j1].fitness, Ind[j1].rank);
640 for (
unsigned int i=0; i<Model->ObservationsCount();i++)
642 fprintf(FileOut,
",%le, %le, %le", Ind[j1].fit_measures[i]);
644 fprintf(FileOut,
"\n");
648 int j = maxfitness();
650 Fitness[current_generation][0] = Ind[j].actual_fitness;
654 {
if (current_generation==0)
656 rtw->SetYRange(0,Ind[j].actual_fitness*1.1);
657 rtw->SetXRange(0,GA_params.nGen);
659 rtw->SetProgress(
double(current_generation)/
double(GA_params.nGen));
660 rtw->AppendPoint(current_generation+1,Ind[j].actual_fitness);
662 QCoreApplication::processEvents();
665 if (current_generation>10)
667 if ((Fitness[current_generation][0] == Fitness[current_generation - 3][0]) && GA_params.shakescale>pow(10.0, -Ind[0].precision[0]))
668 GA_params.shakescale *= GA_params.shakescalered;
671 if ((Fitness[current_generation][0]>Fitness[current_generation - 1][0]) && (GA_params.shakescale<shakescaleini))
672 GA_params.shakescale /= GA_params.shakescalered;
673 GA_params.numenhancements = 0;
676 if (current_generation>50)
678 if (Fitness[current_generation][0] == Fitness[current_generation - 20][0])
680 GA_params.numenhancements *= 1.05;
681 if (GA_params.numenhancements == 0) GA_params.numenhancements = ininumenhancements;
684 if (Fitness[current_generation][0] == Fitness[current_generation - 50][0])
685 GA_params.numenhancements = ininumenhancements * 10;
688 Fitness[current_generation][1] = GA_params.shakescale;
689 Fitness[current_generation][2] = GA_params.pmute;
691 if (current_generation>20)
693 if (GA_params.shakescale == Fitness[current_generation - 20][1])
694 GA_params.shakescale = shakescaleini;
699 MaxFitness = Ind[j].actual_fitness;
701 Fitness[current_generation][0] = Ind[j].actual_fitness;
706 write_to_detailed_GA(
"Cross-over ...");
708 if (GA_params.RCGA ==
true)
713 write_to_detailed_GA(
"Cross-over done! ");
715 write_to_detailed_GA(
"Mutation ...");
717 mutate(GA_params.pmute);
718 write_to_detailed_GA(
"Mutation done!");
719 write_to_detailed_GA(
"Shake...!");
721 write_to_detailed_GA(
"Shake done!");
727 FileOut = fopen(RunFileName.c_str(),
"a");
728 fprintf(FileOut,
"Final Enhancements\n");
730 int j = maxfitness();
732 MaxFitness = Ind[j].actual_fitness;
733 final_params.resize(GA_params.nParam);
736 for (
int k = 0; k<Ind[0].nParams; k++)
738 if (loged[k] == 1) final_params[k] = pow(10, Ind[j].
x[k]);
else final_params[k] = Ind[j].x[k];
739 fprintf(FileOut,
"%s, ", paramname[k].c_str());
740 fprintf(FileOut,
"%le, ", final_params[k]);
741 fprintf(FileOut,
"%le, %le\n", Ind[j].actual_fitness, Ind[j].fitness);
743 for (
unsigned int i=0; i<Model->ObservationsCount();i++)
745 fprintf(FileOut,
",%le, %le, %le\n", Ind[j].fit_measures[i]);
749 assignfitnesses(final_params);
754 rtw->SetProgress(1.0);
755 QCoreApplication::processEvents();
768 double likelihood = 0;
772 for (
int i = 0; i < GA_params.nParam; i++)
773 Model1.SetParameterValue(i, inp[i]);
777 likelihood -= Model1.GetObjectiveFunctionValue();
822 ifstream file(filename);
824 final_params.resize(GA_params.nParam);
825 while (file.eof() ==
false)
827 s = aquiutils::getline(file);
830 if (s[0] ==
"Final Enhancements")
831 for (
int i = 0; i<GA_params.nParam; i++)
833 s = aquiutils::getline(file);
835 write_to_detailed_GA(
"The number of parameters in GA output file does not match the number of unknown parameters");
837 final_params[i] = atof(s[1].c_str());
841 double ret = assignfitnesses(final_params);
850 for (
int j = 0; j<i; j++)
851 if (apply_to_all[j]) l++;
else l += 1;
863 for (
int j = 0; j<GA_params.nParam; j++)
865 if (apply_to_all[j]) l += 1;
else l += 1;
868 if (apply_to_all[j]) l -= 1;
else l--;
878 for (
int i=1; i<GA_params.maxpop; i++)
879 Ind[i].shake(GA_params.shakescale);
886 for (
int i=2; i<GA_params.maxpop; i++)
894 double max_fitness = 1E+308 ;
896 for (
int i=0; i<GA_params.maxpop; i++)
897 if (max_fitness>Ind[i].actual_fitness)
899 max_fitness = Ind[i].actual_fitness;
910 double a = avgfitness();
911 for (
int i=0; i<GA_params.maxpop; i++)
912 sum += (a - Ind[i].fitness)*(a - Ind[i].fitness);
921 double a = avg_inv_actual_fitness();
922 for (
int i=0; i<GA_params.maxpop; i++)
923 sum += (a - 1/Ind[i].actual_fitness)*(a - 1/Ind[i].actual_fitness);
924 return sqrt(sum)/GA_params.maxpop/a;
932 for (
int i=0; i<GA_params.maxpop; i++)
933 sum += Ind[i].actual_fitness;
934 return sum/GA_params.maxpop;
942 for (
int i=0; i<GA_params.maxpop; i++)
943 sum += 1/Ind[i].actual_fitness;
944 return sum/GA_params.maxpop;
952 for (
int i=0; i<GA_params.maxpop; i++)
955 for (
int j=0; j<GA_params.maxpop; j++)
957 if (Ind[i].actual_fitness > Ind[j].actual_fitness) r++;
968 for (
int i=0; i<GA_params.maxpop; i++)
970 Ind[i].fitness = pow(1.0/
static_cast<double>(Ind[i].rank),GA_params.N);
979 for (
int i=0; i<GA_params.maxpop; i++)
985 fitdist.e[0] = Ind[0].fitness/sum;
986 for (
int i=1; i<GA_params.maxpop-1; i++)
988 fitdist.e[i] = fitdist.e[i-1] + Ind[i].fitness/sum;
989 fitdist.s[i] = fitdist.e[i-1];
991 fitdist.s[GA_params.maxpop-1] = fitdist.e[GA_params.maxpop-2];
992 fitdist.e[GA_params.maxpop-1] = 1;
1002 int x_nParam = GA_params.nParam;
1003 vector<int> x_params = params;
1004 GA_params.nParam = 1;
1007 out[0] = assignfitnesses(v);
1009 out.writetofile(filenames.pathname +
"likelihood.txt");
1011 GA_params.nParam = x_nParam;
1018 ifstream file(filename);
1020 initial_pop.resize(1);
1021 initial_pop[0].resize(GA_params.nParam);
1022 while (file.eof() ==
false)
1024 s = aquiutils::getline(file);
1026 {
if (s[0] ==
"Final Enhancements")
1027 for (
int i=0; i<GA_params.nParam; i++)
1029 s = aquiutils::getline(file);
1031 initial_pop[0][i] = atof(s[1].c_str());
1033 initial_pop[0][i] = atof(s[1].c_str());
1044 ifstream file(filename);
1047 while (file.eof() ==
false)
1049 s = aquiutils::getline(file);
1054 for (
int j=0; j<s.size(); j++)
1055 initial_pop.push_back(aquiutils::ATOF(s));
void cross2p(CBinary &B1, CBinary &B2, int p1, int p2)
void cross(CBinary &B1, CBinary &B2, int p)
void cross_RC_L(const CIndividual &I1, const CIndividual &I2, CIndividual &IR1, CIndividual &IR2)
double GetRndUnif(double xmin, double xmax)
Genetic Algorithm optimizer for global parameter estimation.
double MaxFitness
Maximum fitness value in current population.
void fillfitdist()
Fill fitness distribution for roulette wheel selection.
bool SetProperty(const std::string &varname, const std::string &value)
Set GA property from string key-value pair.
std::vector< int > params
Parameter indices being optimized.
int maxfitness()
Find index of individual with maximum fitness.
void assignrank()
Assign ranks to individuals based on fitness.
int get_time_series(int i)
Get time series index for parameter.
double avg_actual_fitness()
Calculate average of actual (untransformed) fitness values.
bool SetProperties(const std::map< std::string, std::string > &arguments)
Set multiple GA properties from map.
GA_Tweaking_parameters GA_params
GA algorithm configuration parameters.
void shake()
Apply shake/perturbation to population.
virtual ~CGA()
Destructor.
std::vector< CIndividual > Ind
Current population of individuals.
GADistribution fitdist
Fitness distribution for parent selection.
CGA operator=(CGA &C)
Assignment operator.
void setnumpop(int n)
Set population size.
double evaluateforward()
Evaluate model forward (compute predictions)
std::vector< std::string > paramname
Names of parameters being optimized.
void Setminmax(int a, double minrange, double maxrange, int prec)
Set parameter bounds and precision.
double avg_inv_actual_fitness()
Calculate average of inverse actual fitness.
void crossoverRC()
Real-coded crossover operation.
CGA()
Default constructor.
void assignfitnesses()
Evaluate fitness for all individuals in population.
void setnparams(int n)
Set number of parameters.
int optimize()
Main optimization loop.
void getinifromoutput(std::string filename)
Initialize population from previous output file.
double stdfitness()
Calculate standard deviation of population fitness.
int getparamno(int i, int ts)
Get parameter index for variable and time series.
void mutate(double mu)
Apply mutation to population.
std::vector< CIndividual > Ind_old
Previous generation's population.
void initialize()
Initialize GA structures and allocate memory.
void crossover()
Perform crossover operation on population.
double variancefitness()
Calculate variance of population fitness.
std::vector< int > loged
Flags indicating if parameters are log-transformed.
double getfromoutput(std::string filename)
Read best solution from previous output file.
void write_to_detailed_GA(std::string s)
Write detailed GA information to file.
void InitiatePopulation()
Create initial population.
void assignfitness_rank(double N)
Assign fitness using rank-based scheme.
void getinitialpop(std::string filename)
Read initial population from file.
double avgfitness()
Calculate average fitness of population.
_filenames filenames
File paths for GA input/output.
std::vector< int > precision
std::vector< double > minrange
std::vector< double > maxrange
@ lognormal
Lognormal distribution: ln(x) ~ N(μ, σ²), for strictly positive variables.
@ low
Lower bound of the parameter range.
@ high
Upper bound of the parameter range.
int maxpop
Maximum population size (number of individuals)