diff --git a/example-configs/density_skj_fat/Snakefile b/example-configs/density_skj_fat/Snakefile
index b50ad16..134779e 100644
--- a/example-configs/density_skj_fat/Snakefile
+++ b/example-configs/density_skj_fat/Snakefile
@@ -3,17 +3,14 @@ import csv
import pandas as pd
import matplotlib.pyplot as plt
-# snakemake --jobs 10 --cluster "sbatch -p genoa --mem=20g --nodes=1 --ntasks-per-node={threads} --cpus-per-task=1 --time=01:30:00 --extra-node-info=1:*:1 --distribution=*:block:* --mem-bind=local"
+# snakemake --jobs 10 --cluster "sbatch -p genoa --mem=60g --nodes=1 --ntasks-per-node={threads} --cpus-per-task=1 --time=01:30:00 --extra-node-info=1:*:1 --distribution=*:block:* --mem-bind=local"
#
# MPI benchmarking configurations
-#N_VALUES = [2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,28,29,30,31,32]
-#N_VALUES = [2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,36,40,50]
-N_VALUES = [2,3,4,5,6,7,8,9,10,11,12,14,16,17,18,20,22,24,26,28,32,36,40,44,51]
-#N_VALUES = [10, 20, ]
-REPEATS = range(0,6)
-#HOSTS = ["milan", "genoa"]
+PROCS = [3, 11, 21, 31, 41, 51, 61, 71, 81, 91, 101]
+REPEATS = range(0,3)
HOSTS = ["genoa",]
+CASES = ["aplus", "aplus3"]
# -----------------------------
# Top-level workflow
@@ -27,20 +24,17 @@ rule all:
# -----------------------------
rule run_mpi:
output:
- main="results/run_{n}_{r}_{h}-main.out",
- best="results/run_{n}_{r}_{h}-best.out"
+ exp="results/run_{n}_{r}_{h}_{c}.out",
params:
- exe_main="../../bin/seapodym_cohort-main",
- exe_best="../../bin/seapodym_cohort-best",
- parfile="skj_fat.xml"
+ parfile="../density_skj_fat/skj_Fat.xml"
threads:
lambda wc: int(wc.n)
shell:
r"""
set -x
- cd ~/nesi99999/seapodym-codebase-test_shm/
+ cd ~/nesi99999/seapodym-codebase-exp/
- source sourceme-main.sh
+ source sourceme-exp.sh
cd example-configs/density_skj_fat
mkdir -p results
@@ -51,19 +45,14 @@ rule run_mpi:
rm -rf $rundir
mkdir $rundir
cd $rundir
- # copy the input file
- cp ../density_skj_fat/{params.parfile} .
-
-
- # run main
- echo "jobid=$SLURM_JOB_ID" > ../density_skj_fat/{output.main}
- srun --distribution=block:block --cpu-bind=cores --cpus-per-task=1 \
- {params.exe_main} -s {params.parfile} >> ../density_skj_fat/{output.main} 2>&1
+
+ # create directories
+ mkdir -p output/output_F0/
- # run best
- echo "jobid=$SLURM_JOB_ID" > ../density_skj_fat/{output.best}
- srun --distribution=block:block --cpu-bind=cores --cpus-per-task=1 \
- {params.exe_best} -s {params.parfile} >> ../density_skj_fat/{output.best} 2>&1
+ # run
+ echo "jobid=$SLURM_JOB_ID" > ../density_skj_fat/{output.exp}
+ srun --ntasks={threads} --distribution=block:block --cpu-bind=cores --cpus-per-task=1 \
+ ../../bin/seapodym_cohort_{wildcards.c} -s {params.parfile} >> ../density_skj_fat/{output.exp} 2>&1
cd ../
# clean up
@@ -77,13 +66,12 @@ rule run_mpi:
# -----------------------------
rule collect_results:
input:
- expand("results/run_{n}_{r}_{h}-main.out", n=N_VALUES, r=REPEATS, h=HOSTS),
- expand("results/run_{n}_{r}_{h}-best.out", n=N_VALUES, r=REPEATS, h=HOSTS)
+ expand("results/run_{n}_{r}_{h}_{c}.out", n=PROCS, r=REPEATS, h=HOSTS, c=CASES),
output:
"results/timings.csv"
shell:
r"""
module load Python
- python assemble_timings.py -d results -o results/timings.csv
+ python assemble_timings_aplus.py -d results -o results/timings.csv
"""
diff --git a/example-configs/density_skj_fat/assemble_timings_aplus.py b/example-configs/density_skj_fat/assemble_timings_aplus.py
new file mode 100644
index 0000000..8933451
--- /dev/null
+++ b/example-configs/density_skj_fat/assemble_timings_aplus.py
@@ -0,0 +1,48 @@
+import re
+import glob
+import defopt
+import csv
+
+
+def main(*, dir : str='results', output : str='results/timings.csv'):
+ """
+ Assemble timings into a csv file
+ :param dir directory
+ """
+ pattern = re.compile(r"time manager\s*=\s*([0-9.]+)")
+ rows = []
+
+ for f in glob.glob(dir + '/run*.out'):
+
+ print(f'working on {f}...')
+
+ parts = f.split("/")[-1].replace(".out","").split("_")
+ n = int(parts[1])
+ r = int(parts[2])
+ host = parts[3]
+ case = parts[4]
+
+ jobid = None
+ manager_time = None
+
+ with open(f) as fh:
+ for line in fh:
+ if line.startswith("jobid="):
+ jobid = line.split("=")[1].strip()
+ m = pattern.search(line)
+ if m:
+ manager_time = float(m.group(1))
+ print(f'manager time = {manager_time}')
+
+ rows.append((n, jobid, host, case, manager_time))
+
+ rows.sort()
+
+ with open(output, "w", newline="") as csvfile:
+ writer = csv.writer(csvfile)
+ writer.writerow(["nprocs","jobid","host","case", "manager_time"])
+ writer.writerows(rows)
+
+if __name__ == '__main__':
+ defopt.run(main)
+
diff --git a/example-configs/density_skj_fat/plot_timings_aplus.py b/example-configs/density_skj_fat/plot_timings_aplus.py
new file mode 100644
index 0000000..685b0f4
--- /dev/null
+++ b/example-configs/density_skj_fat/plot_timings_aplus.py
@@ -0,0 +1,59 @@
+import os
+import pandas as pd
+import matplotlib.pyplot as plt
+import numpy as np
+import defopt
+
+
+def main(*, input_file: str='results/timings.csv', output_file: str='results/timings.png', show: bool=False):
+ """
+ input_file: CSV input file (e.g. results/timings.csv)
+ output_file: PNG output file
+ show: set to True to display
+ """
+
+ # Load CSV
+ df = pd.read_csv(input_file)
+
+ # Ensure manager_time column exists
+ if "manager_time" not in df.columns:
+ print(f"ERROR: 'manager_time' column not found in CSV. Cannot plot.")
+
+ # Drop rows with missing manager_time
+ df = df.dropna(subset=["manager_time"])
+ if df.empty:
+ print(f"ERROR: No valid 'manager_time' values found. Cannot plot.")
+
+ # Plot curves for Milan & Genoa
+ cols = {
+ 'aplus': 'b',
+ 'aplus3': 'm',
+ }
+ plt.figure(figsize=(8,6))
+ for case in df["case"].unique():
+ df2 = df[df.case == case]
+ df_min = df2.groupby(["nprocs"])["manager_time"].min().reset_index()
+ df_avg = df2.groupby(["nprocs"])["manager_time"].mean().reset_index()
+ df_max = df2.groupby(["nprocs"])["manager_time"].max().reset_index()
+
+ col = cols[case]
+ plt.loglog(df_min["nprocs"], df_min["manager_time"], col + '-.', label=case + ' min')
+ plt.loglog(df_avg["nprocs"], df_avg["manager_time"], col + '-', label=case + ' avg')
+ plt.loglog(df_max["nprocs"], df_max["manager_time"], col + '--', label=case + ' max')
+
+ plt.loglog([1,100], 1000./np.array([1,100]), 'k-', label='ideal')
+
+ plt.xlabel("Number of processes (nprocs)")
+ plt.ylabel("Manager time s")
+ plt.title("Seapodym cohort timings 1983-2022 skj_Fat.xml A+")
+ plt.xticks(sorted(df["nprocs"].unique()))
+ plt.grid(True)
+ plt.legend()
+ plt.tight_layout()
+ plt.savefig(output_file)
+ if show:
+ plt.show()
+ print(f"plot saved to {output_file}")
+
+if __name__ == '__main__':
+ defopt.run(main)
diff --git a/example-configs/density_skj_fat/rungenoa.sl b/example-configs/density_skj_fat/rungenoa.sl
index 27b7428..67acfc9 100644
--- a/example-configs/density_skj_fat/rungenoa.sl
+++ b/example-configs/density_skj_fat/rungenoa.sl
@@ -3,13 +3,9 @@
#SBATCH --time=00:10:00 # walltime (adjust as needed)
#SBATCH --partition=genoa
#SBATCH --nodes=1
-#SBATCH --ntasks=24
-#SBATCH --mem-per-cpu=4g
+#SBATCH --ntasks=51
+#SBATCH --mem=25g
#SBATCH --cpus-per-task=1
-#SBATCH --output=seapodym_%j.out
-#SBATCH --error=seapodym_%j.err
-##SBATCH --profile task
-##SBATCH --acctg-freq=1
#SBATCH --extra-node-info=1:*:1
#SBATCH --distribution=*:block:*
#SBATCH --mem-bind=local
@@ -20,4 +16,4 @@ module load gimkl/2020a
rm log_*.txt
# Run the MPI job
-srun ../../bin/seapodym_cohort -s skj_fat.xml >& results/n${SLURM_NTASKS}.txt
+srun ../../bin/seapodym_cohort -s skj_fat.xml
diff --git a/example-configs/density_skj_fat/skj_fat.xml b/example-configs/density_skj_fat/skj_fat.xml
index 7081781..d02bf59 100644
--- a/example-configs/density_skj_fat/skj_fat.xml
+++ b/example-configs/density_skj_fat/skj_fat.xml
@@ -244,7 +244,7 @@
- 1 2 47
+ 1 2 97
@@ -255,7 +255,12 @@
30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30
- 30 330
+ 30 30 30 30 30 30 30 30 30 30 30 30
+ 30 30 30 30 30 30 30 30 30 30 30 30
+ 30 30 30 30 30 30 30 30 30 30 30 30
+ 30 30 30 30 30 30 30 30 30 30 30 30
+ 30 30 30 2970
+
@@ -263,7 +268,12 @@
48.33 51.19 53.86 56.36 58.70 60.88 62.92 64.83 66.61 68.27 69.83 71.28
72.64 73.91 75.10 76.21 77.25 78.22 79.12 79.97 80.76 81.50 82.19 82.83
83.50 83.50 83.50 83.50 83.50 83.50 83.50 83.50 83.50 83.50 83.50 83.50
- 85.00 87.96
+ 85.00 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96
+ 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96
+ 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96
+ 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96
+ 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96 87.96
+
@@ -271,7 +281,13 @@
2.27 2.73 3.21 3.72 4.23 4.76 5.30 5.83 6.36 6.89 7.40 7.91
8.41 8.89 9.36 9.81 10.25 10.66 11.07 11.45 11.82 12.17 12.51 12.83
13.5 13.5 13.5 13.5 13.50 13.50 13.50 13.50 13.50 13.50 13.50 13.50
- 14.0 15.56
+ 14.0 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56
+ 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56
+ 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56
+ 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56
+ 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56
+ 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56 15.56
+
diff --git a/src/SeapodymCohort.cpp b/src/SeapodymCohort.cpp
index e5506ce..247ad01 100755
--- a/src/SeapodymCohort.cpp
+++ b/src/SeapodymCohort.cpp
@@ -3,6 +3,8 @@
#include "Date.h"
#include "sys/stat.h"
#include
+#include
+#include
#include "DistDataCollector.h"
#include "DataProvider.h"
@@ -30,7 +32,7 @@ std::vector SeapodymCohort::GetCohortDensity()
return vec;
}
-void SeapodymCohort::InitializeCohort(dvar_vector& x, DistDataCollector& dataCollector, const bool writeoutputfiles)
+void SeapodymCohort::InitializeCohort(dvar_vector& x, DistDataCollector& dataCollector, int numTimeSteps, const bool writeoutputfiles)
{
double t_all = MPI_Wtime();
@@ -71,9 +73,21 @@ double t_flush_acc = 0.0, t_copy_acc = 0.0;
dataCollector.getAsync(chunk_id, data.data());
double t_f = MPI_Wtime();
- dataCollector.flush(); // now the data are ready to be used
+ dataCollector.flush(); // now the data are ready to be used
t_flush_acc += MPI_Wtime() - t_f;
+ if (std::isnan(data[0])) {
+ // NaN (DistDataCollector::BAD_VALUE) here means chunk_id was
+ // never written before this cohort tried to read it - a
+ // missing/out-of-order dependency, not a numerical blow-up.
+ // Note: BAD_VALUE == NaN can never be detected with ==
+ // (NaN != NaN by definition), hence std::isnan() here.
+ std::cerr << "ERROR: cohort " << cohort_id
+ << " (tstart_cohort=" << tstart_cohort << ") read BAD_VALUE (NaN) "
+ << "from chunk " << chunk_id << " (age " << aa << ")\n";
+ MPI_Abort(MPI_COMM_WORLD, 1);
+ }
+
double t_c = MPI_Wtime();
int index = 0;
for (int i = map.imin1; i <= map.imax1; i++){
@@ -88,6 +102,42 @@ double t_c = MPI_Wtime();
}
t_copy_acc += MPI_Wtime() - t_c;
}
+
+ if (aPlusEnabled){
+ // Plus group's density as of the end of t-1. SpawningBiomass_comp's
+ // own loop (a < param->sp_nb_cohorts[sp]) always included the
+ // oldest age class, A+ or not; nb_age_class here IS exactly that
+ // oldest (A+) index, since it excludes A+ from the "normal" range
+ // by construction. A+ publishes its density under this same
+ // chunk-id formula each step - see runAPlusWorker() in
+ // main_cohort.cpp.
+ int chunk_id = nb_age_class * numTimeSteps + (tstart_cohort - 1);
+ dataCollector.getAsync(chunk_id, data.data());
+
+double t_f2 = MPI_Wtime();
+ dataCollector.flush();
+t_flush_acc += MPI_Wtime() - t_f2;
+
+ if (std::isnan(data[0])) {
+ std::cerr << "ERROR: cohort " << cohort_id
+ << " (tstart_cohort=" << tstart_cohort << ") read BAD_VALUE (NaN) "
+ << "from A+ chunk " << chunk_id << " (t=" << (tstart_cohort - 1) << ")\n";
+ MPI_Abort(MPI_COMM_WORLD, 1);
+ }
+
+double t_c2 = MPI_Wtime();
+ int index = 0;
+ for (int i = map.imin1; i <= map.imax1; i++){
+ const int jmin1 = map.jinf1[i];
+ const int jmax1 = map.jsup1[i];
+ for (int j = jmin1 ; j <= jmax1; j++){
+ mat.dvarDensity(0,nb_age_class).elem_value(i,j) = data[index];
+ index++;
+ }
+ }
+t_copy_acc += MPI_Wtime() - t_c2;
+ }
+
dataCollector.endEpoch(); // should be as late as possible
double t_total = MPI_Wtime() - t_block;
@@ -119,6 +169,50 @@ double t_sp = MPI_Wtime();
time_spawning += MPI_Wtime() - t_sp;
}
+ FinishInitialize(writeoutputfiles);
+
+time_init_cohort_restart += t_rs;
+time_init_cohort_spawning += MPI_Wtime() - t_all - t_rs;
+}
+
+void SeapodymCohort::InitializeAPlus(dvar_vector& x, const std::vector& mergedDensity, bool seedFromFile)
+{
+ //Reset model parameters:
+ reset(x);
+
+ //----------------------------------------------//
+ // ALLOCATE AND INITIALIZE COHORT DENSITY //
+ //----------------------------------------------//
+ dvarCohortDensity.allocate(map.imin1, map.imax1, map.jinf1, map.jsup1);
+ if (seedFromFile){
+ // t=0: no upstream task dependency; the plus group's opening balance
+ // is just the initial condition for its age bin (age_start ==
+ // nb_age_class), read from file exactly like any other initial cohort.
+ RestoreDistributions(mat.nb_age_built);
+ dvarCohortDensity = mat.init_density_species(0,age_start);
+ } else {
+ // t>0: mergedDensity already holds (previous A+ pool) + (individuals
+ // that just graduated into the top age class this step), flattened
+ // in the same map.imin1..imax1/jinf1..jsup1 order GetCohortDensity()
+ // uses. This is the density stepForward() will apply this step's
+ // mortality/movement/feeding-habitat dynamics to - the AgePlus merge
+ // happens here, before dynamics, not instead of them.
+ int index = 0;
+ for (int i = map.imin1; i <= map.imax1; i++){
+ const int jmin1 = map.jinf1[i];
+ const int jmax1 = map.jsup1[i];
+ for (int j = jmin1 ; j <= jmax1; j++){
+ dvarCohortDensity.elem_value(i,j) = mergedDensity[index];
+ index++;
+ }
+ }
+ }
+
+ FinishInitialize(false);
+}
+
+void SeapodymCohort::FinishInitialize(bool writeoutputfiles)
+{
if (writeoutputfiles){
if (!param->gcalc())
ConsoleOutput(0,0);
@@ -126,19 +220,19 @@ time_spawning += MPI_Wtime() - t_sp;
//----------------------------------------------//
// PRECOMPUTE VARIABLES USED IN COHORT MODELING //
- //----------------------------------------------//
+ //----------------------------------------------//
//precompute thermal habitat parameters
for (int sp=0; sp < nb_species; sp++){
func.Vars_at_age_precomp(*param,sp);
func.mortality_range_age_comp(*param,mat,sp);
-
+
//precompute seasonal switch function
if (param->seasonal_migrations[sp]){
func.Seasonal_switch_year_precomp(*param,mat,map,
value(param->dvarsSpawning_season_peak[sp]),
value(param->dvarsSpawning_season_start[sp]),sp);
}
- }
+ }
getDate(jday, t_count);// In order to get qtr
if (param->type_oxy==1)
@@ -149,9 +243,6 @@ time_spawning += MPI_Wtime() - t_sp;
age = age_start;
past_month = month;
past_qtr = qtr;
-
-time_init_cohort_restart += t_rs;
-time_init_cohort_spawning += MPI_Wtime() - t_all - t_rs;
}
void SeapodymCohort::stepForward(bool writeoutputfiles)
diff --git a/src/SeapodymCohort.h b/src/SeapodymCohort.h
index ba98c0d..8c90299 100755
--- a/src/SeapodymCohort.h
+++ b/src/SeapodymCohort.h
@@ -16,9 +16,22 @@ class SeapodymCohort : public SeapodymCoupled
{
public:
SeapodymCohort(){/*DoesNothing*/};
- SeapodymCohort(const char* parfile, int cohortId) : SeapodymCoupled(parfile) {
+ // aPlusOn selects whether the A+ (plus group) bin is carved out of the
+ // normal diagonal cohort scheme (default, matches main_cohort.cpp's
+ // default) or folded back in as an ordinary aging cohort, reproducing
+ // pre-A+ behavior. Exposed so main_cohort.cpp's -no-aplus flag can
+ // request the old behavior for regression comparison.
+ SeapodymCohort(const char* parfile, int cohortId, bool aPlusOn = true) : SeapodymCoupled(parfile) {
cohort_id = cohortId;
- nb_age_class = param->sp_nb_cohorts[0];
+ aPlusEnabled = aPlusOn;
+ // nb_age_class excludes the A+ (plus group) bin when aPlusEnabled: the
+ // diagonal cohort-task scheme only ages "normal" cohorts through
+ // nb_age_class steps; the A+ bin (age index sp_nb_cohorts[0]-1) is
+ // handled separately as its own chain of accumulator tasks (see
+ // main_cohort.cpp / SeapodymCohortDependencyAnalyzer). When disabled,
+ // nb_age_class reverts to sp_nb_cohorts[0], i.e. A+ is just the last
+ // ordinary aging cohort, as before this feature existed.
+ nb_age_class = param->sp_nb_cohorts[0] - (aPlusEnabled ? 1 : 0);
// Get starting age_class and start time from cohort_id
if (cohort_id >= nb_age_class){
@@ -33,22 +46,35 @@ class SeapodymCohort : public SeapodymCoupled
double time_overhead;
//double run_cohort(dvar_vector x, const bool writeoutputfiles = false) { return OnRunCohort(x, writeoutputfiles); }
- void init_cohort(dvar_vector x, DistDataCollector& dataCollector, const bool writeoutputfiles = false) { return InitializeCohort(x, dataCollector, writeoutputfiles); }
+ // numTimeSteps is only needed to locate A+'s published density chunk
+ // (aplus_chunk_id = nb_age_class*numTimeSteps + (tstart_cohort-1)) when
+ // this cohort is being initialized from spawning and aPlusEnabled - see
+ // InitializeCohort()'s spawning branch and main_cohort.cpp's matching
+ // nb_age_class*numTimeSteps + t formula in runAPlusWorker().
+ void init_cohort(dvar_vector x, DistDataCollector& dataCollector, int numTimeSteps, const bool writeoutputfiles = false) { return InitializeCohort(x, dataCollector, numTimeSteps, writeoutputfiles); }
void prerun_model();
void OnRunFirstStep();
double Checksum();
std::vector GetCohortDensity();
- int getChunkId(int step) {
- int row = cohort_id - nb_age_class + 1 + step;
+
+ // Chunk-id formula for a normal (non-A+) cohort task, factored out as a
+ // static so callers (e.g. main_cohort.cpp) can look up the chunk of *any*
+ // task/step pair, not just "this" cohort's own.
+ static int computeChunkId(int taskId, int step, int numAgeGroups) {
+ int row = taskId - numAgeGroups + 1 + step;
int col = step;
- if (cohort_idsp_nb_cohorts[0];
+ nb_age_class = param->sp_nb_cohorts[0] - (aPlusEnabled ? 1 : 0); // see constructor
// Get starting age_class and start time from cohort_id
if (cohort_id >= nb_age_class){
age_start = 0;
@@ -60,6 +86,40 @@ class SeapodymCohort : public SeapodymCoupled
t_count = tstart_cohort+1;
}
+ // Sets this object up to represent the A+ (plus group) accumulator task
+ // for absolute time index t (t=0 is the initial condition, before any
+ // time step has elapsed). age/age_start are pinned at nb_age_class (one
+ // past the last normal age, i.e. the A+ bin) rather than advancing:
+ // unlike restart(), A+ is not a persisting trajectory with a "next age"
+ // to advance into - it is the same SeapodymCohort object reused for
+ // every time step by main_cohort.cpp's dedicated A+ worker loop, with
+ // restartAPlus(t)/init_cohort_aplus() called fresh before each
+ // stepForward() (see runAPlusWorker() in main_cohort.cpp). tstart_cohort
+ // /t_count follow the same convention restart() uses for a cohort "born"
+ // at time t, which is what makes stepForward()'s existing calendar-date
+ // and forcing-data lookups (both keyed off tstart_cohort/t_count) come
+ // out correct without any further changes to stepForward() itself - and
+ // what makes reusing this object safe: every field stepForward() reads
+ // is re-pinned by restartAPlus()/FinishInitialize() before each call.
+ void restartAPlus(int t){
+ age_start = nb_age_class;
+ age = nb_age_class;
+ tstart_cohort = t;
+ t_count = t + 1;
+ }
+
+ // Initializes the A+ task's density either from the actual initial
+ // condition (seedFromFile=true, used only for t=0, which has no upstream
+ // task dependency) or from mergedDensity - the previous A+ pool plus the
+ // individuals that just graduated into the top age class, already summed
+ // by the caller, flattened in the same map.imin1..imax1/jinf1..jsup1
+ // order as GetCohortDensity(). Either way, stepForward() then runs the
+ // same adult dynamics (mortality, movement, feeding habitat) on it that
+ // any other cohort's step would get.
+ void init_cohort_aplus(dvar_vector x, const std::vector& mergedDensity, bool seedFromFile) {
+ return InitializeAPlus(x, mergedDensity, seedFromFile);
+ }
+
private:
int dtau;
int nbt_before_first_recruitment;
@@ -78,6 +138,7 @@ class SeapodymCohort : public SeapodymCoupled
int age_start;
int tstart_cohort;
int nb_age_class;
+ bool aPlusEnabled;
DataProvider* dp_ = nullptr;
@@ -98,7 +159,14 @@ class SeapodymCohort : public SeapodymCoupled
int pop_built;
- void InitializeCohort(dvar_vector& x, DistDataCollector& dataCollector, const bool writeoutputfiles = false);
+ void InitializeCohort(dvar_vector& x, DistDataCollector& dataCollector, int numTimeSteps, const bool writeoutputfiles = false);
+ void InitializeAPlus(dvar_vector& x, const std::vector& mergedDensity, bool seedFromFile);
+ // Setup shared by InitializeCohort() and InitializeAPlus(): precomputed
+ // per-age habitat/mortality parameters, calendar date, O2 climatology,
+ // and the age/past_month/past_qtr bookkeeping stepForward() relies on.
+ // Factored out so both initialization paths stay in lockstep instead of
+ // risking drift between two copies of the same ~15 lines.
+ void FinishInitialize(bool writeoutputfiles);
public:
void stepForward(const bool writeoutputfiles = false);
diff --git a/src/main_cohort.cpp b/src/main_cohort.cpp
index 1c5920e..ef45e08 100755
--- a/src/main_cohort.cpp
+++ b/src/main_cohort.cpp
@@ -1,8 +1,12 @@
#include
#include
#include
+#include
#include
#include // std::accumulate
+#include // std::copy
+#include