aegis_sim.dataclasses.population
1import numpy as np 2import pickle 3import pathlib 4 5from aegis_sim.dataclasses.genomes import Genomes 6from aegis_sim.dataclasses.phenotypes import Phenotypes 7from aegis_sim import submodels 8 9 10class Population: 11 """Population data 12 13 Contains demographic, genetic and phenotypic data of living individuals. 14 """ 15 16 attrs = ( 17 "genomes", 18 "ages", 19 "births", 20 "birthdays", 21 "generations", 22 "phenotypes", 23 "infection", 24 "sizes", 25 "sexes", 26 "ancestry", 27 "lineage_id", 28 "parent_lineage_id", 29 "positions", 30 ) 31 32 def __init__( 33 self, 34 genomes: Genomes, 35 ages, 36 births, 37 birthdays, 38 phenotypes: Phenotypes, 39 infection, 40 sizes, 41 sexes, 42 generations=None, 43 ancestry=None, 44 lineage_id=None, 45 parent_lineage_id=None, 46 positions=None, 47 ): 48 self.genomes = genomes 49 self.ages = ages 50 self.births = births 51 self.birthdays = birthdays 52 self.phenotypes = phenotypes 53 self.infection = infection 54 self.sizes = sizes 55 self.sexes = sexes 56 self.generations = generations 57 # ancestry: bool array, same shape as genomes.array; True = introgressed from source pop. 58 # None when introgression tracking is disabled (standard runs). 59 self.ancestry = ancestry 60 # lineage_id, parent_lineage_id: int64 arrays of length len(genomes). 61 # Both None when LINEAGE_TRACING is disabled (standard runs). 62 self.lineage_id = lineage_id 63 self.parent_lineage_id = parent_lineage_id 64 # positions: int32 array of shape (n, 2) holding (q, r) axial hex coordinates 65 # on the toroidal lattice. None when LATTICE_MODE is disabled (standard runs). 66 self.positions = positions 67 68 assert isinstance(phenotypes, Phenotypes) 69 70 if not ( 71 len(genomes) 72 == len(ages) 73 == len(births) 74 == len(birthdays) 75 == len(phenotypes) 76 == len(infection) 77 == len(sizes) 78 == len(sexes) 79 # == len(generations) 80 ): 81 raise ValueError("Population attributes must have equal length") 82 83 def __setstate__(self, state): 84 # Backward compat: pickles saved before ancestry/lineage/positions fields were added 85 self.__dict__.update(state) 86 if "ancestry" not in self.__dict__: 87 self.ancestry = None 88 if "lineage_id" not in self.__dict__: 89 self.lineage_id = None 90 if "parent_lineage_id" not in self.__dict__: 91 self.parent_lineage_id = None 92 if "positions" not in self.__dict__: 93 self.positions = None 94 95 def __len__(self): 96 """Return the number of living individuals.""" 97 return len(self.genomes) 98 99 def __getitem__(self, index): 100 """Return a subpopulation.""" 101 return Population( 102 genomes=self.genomes.get(individuals=index), 103 ages=self.ages[index], 104 births=self.births[index], 105 birthdays=self.birthdays[index], 106 phenotypes=self.phenotypes.get(individuals=index), 107 infection=self.infection[index], 108 sizes=self.sizes[index], 109 sexes=self.sexes[index], 110 generations=self.generations[index] if self.generations is not None else None, 111 ancestry=self.ancestry[index] if self.ancestry is not None else None, 112 lineage_id=self.lineage_id[index] if self.lineage_id is not None else None, 113 parent_lineage_id=self.parent_lineage_id[index] if self.parent_lineage_id is not None else None, 114 positions=self.positions[index] if self.positions is not None else None, 115 ) 116 117 def __imul__(self, index): 118 """Redefine itself as its own subpopulation.""" 119 for attr in self.attrs: 120 if attr == "genomes": 121 self.genomes.keep(individuals=index) 122 elif attr == "phenotypes": 123 self.phenotypes.keep(individuals=index) 124 elif attr == "generations": 125 self.generations = None 126 elif attr == "ancestry": 127 if self.ancestry is not None: 128 self.ancestry = self.ancestry[index] 129 elif attr in ("lineage_id", "parent_lineage_id", "positions"): 130 current = getattr(self, attr) 131 if current is not None: 132 setattr(self, attr, current[index]) 133 else: 134 setattr(self, attr, getattr(self, attr)[index]) 135 return self 136 137 def __iadd__(self, population): 138 """Merge with another population.""" 139 140 for attr in self.attrs: 141 if attr == "genomes": 142 self.genomes.add(population.genomes) 143 elif attr == "phenotypes": 144 assert isinstance(population.phenotypes, Phenotypes) 145 self.phenotypes.add(population.phenotypes) 146 elif attr == "generations": 147 self.generations = None 148 elif attr == "ancestry": 149 if self.ancestry is not None and population.ancestry is not None: 150 self.ancestry = np.concatenate([self.ancestry, population.ancestry]) 151 elif self.ancestry is not None or population.ancestry is not None: 152 # one side has ancestry tracking, the other doesn't — treat missing as all-native 153 a = self.ancestry if self.ancestry is not None else np.zeros(self.genomes.array.shape, dtype=np.bool_) 154 b = population.ancestry if population.ancestry is not None else np.zeros(population.genomes.array.shape, dtype=np.bool_) 155 self.ancestry = np.concatenate([a, b]) 156 elif attr in ("lineage_id", "parent_lineage_id"): 157 self_val = getattr(self, attr) 158 other_val = getattr(population, attr) 159 if self_val is None and other_val is None: 160 continue 161 # If only one side has lineage IDs, treat the missing side as -1 sentinels. 162 # (No lineage tracking on one side means we cannot reconstruct ancestry — keep what we have.) 163 if self_val is None: 164 self_val = np.full(len(self.genomes), -1, dtype=np.int64) 165 if other_val is None: 166 other_val = np.full(len(population.genomes), -1, dtype=np.int64) 167 setattr(self, attr, np.concatenate([self_val, other_val])) 168 elif attr == "positions": 169 self_val = self.positions 170 other_val = population.positions 171 if self_val is None and other_val is None: 172 continue 173 # Merging spatial + non-spatial populations is unusual. If one side has 174 # positions and the other doesn't, sentinel-fill (-1, -1) for the missing side. 175 # Lattice submodel must claim cells for these once the merge is realized. 176 if self_val is None: 177 self_val = np.full((len(self.genomes), 2), -1, dtype=np.int32) 178 if other_val is None: 179 other_val = np.full((len(population.genomes), 2), -1, dtype=np.int32) 180 self.positions = np.concatenate([self_val, other_val]) 181 else: 182 val = np.concatenate([getattr(self, attr), getattr(population, attr)]) 183 setattr(self, attr, val) 184 return self 185 186 # def shuffle(self): 187 # order = np.random.arange(len(self)) 188 # np.random.shuffle(order) 189 # self *= order 190 191 @staticmethod 192 def load_pickle_from(path: pathlib.Path): 193 assert path.exists(), f"pickle_path {path} does not exist" 194 with open(path, "rb") as file_: 195 return pickle.load(file_) 196 197 def save_pickle_to(self, path): 198 with open(path, "wb") as file_: 199 pickle.dump(self, file_) 200 201 @staticmethod 202 def initialize(n, AGE_LIMIT): 203 from aegis_sim.parameterization import parametermanager 204 from aegis_sim import variables 205 206 genomes = Genomes(submodels.architect.architecture.init_genome_array(n)) 207 ages = np.random.randint(low=0, high=AGE_LIMIT, size=n, dtype=np.int32) 208 births = np.zeros(n, dtype=np.int32) 209 birthdays = np.zeros(n, dtype=np.int32) 210 # generations = np.zeros(n, dtype=np.int32) 211 generations = None 212 213 phenotypes = submodels.architect.__call__(genomes) 214 assert isinstance(phenotypes, Phenotypes) 215 216 infection = np.zeros(n, dtype=np.int32) 217 sizes = np.zeros(n, dtype=np.float32) 218 sexes = submodels.sexsystem.get_sex(n) 219 220 if parametermanager.parameters.LINEAGE_TRACING: 221 lineage_id = variables.next_lineage_ids(n) 222 parent_lineage_id = np.full(n, -1, dtype=np.int64) 223 else: 224 lineage_id = None 225 parent_lineage_id = None 226 227 # Spatial-model: when LATTICE_MODE is on, assign each individual a unique cell 228 # on the toroidal hex lattice. The lattice submodel owns the cell-occupancy 229 # bookkeeping; here we just record each individual's (q, r) coords. When 230 # LATTICE_MODE is off (default), positions stays None and behavior is unchanged. 231 if parametermanager.parameters.LATTICE_MODE: 232 positions = submodels.lattice.assign_initial_positions(n) 233 else: 234 positions = None 235 236 return Population( 237 genomes=genomes, 238 ages=ages, 239 births=births, 240 birthdays=birthdays, 241 generations=generations, 242 phenotypes=phenotypes, 243 infection=infection, 244 sizes=sizes, 245 sexes=sexes, 246 ancestry=None, 247 lineage_id=lineage_id, 248 parent_lineage_id=parent_lineage_id, 249 positions=positions, 250 ) 251 252 @staticmethod 253 def make_eggs( 254 offspring_genomes: Genomes, 255 step, 256 offspring_sexes, 257 parental_generations, 258 offspring_ancestry=None, 259 offspring_lineage_id=None, 260 offspring_parent_lineage_id=None, 261 offspring_positions=None, 262 ): 263 n = len(offspring_genomes) 264 eggs = Population( 265 genomes=offspring_genomes, 266 ages=np.zeros(n, dtype=np.int32), 267 births=np.zeros(n, dtype=np.int32), 268 birthdays=np.zeros(n, dtype=np.int32) + step, 269 generations=None, 270 phenotypes=Phenotypes.init_phenotype_array(n), 271 infection=np.zeros(n, dtype=np.int32), 272 sizes=np.zeros(n, dtype=np.float32), 273 sexes=offspring_sexes, 274 ancestry=offspring_ancestry, 275 lineage_id=offspring_lineage_id, 276 parent_lineage_id=offspring_parent_lineage_id, 277 positions=offspring_positions, 278 ) 279 return eggs
class
Population:
11class Population: 12 """Population data 13 14 Contains demographic, genetic and phenotypic data of living individuals. 15 """ 16 17 attrs = ( 18 "genomes", 19 "ages", 20 "births", 21 "birthdays", 22 "generations", 23 "phenotypes", 24 "infection", 25 "sizes", 26 "sexes", 27 "ancestry", 28 "lineage_id", 29 "parent_lineage_id", 30 "positions", 31 ) 32 33 def __init__( 34 self, 35 genomes: Genomes, 36 ages, 37 births, 38 birthdays, 39 phenotypes: Phenotypes, 40 infection, 41 sizes, 42 sexes, 43 generations=None, 44 ancestry=None, 45 lineage_id=None, 46 parent_lineage_id=None, 47 positions=None, 48 ): 49 self.genomes = genomes 50 self.ages = ages 51 self.births = births 52 self.birthdays = birthdays 53 self.phenotypes = phenotypes 54 self.infection = infection 55 self.sizes = sizes 56 self.sexes = sexes 57 self.generations = generations 58 # ancestry: bool array, same shape as genomes.array; True = introgressed from source pop. 59 # None when introgression tracking is disabled (standard runs). 60 self.ancestry = ancestry 61 # lineage_id, parent_lineage_id: int64 arrays of length len(genomes). 62 # Both None when LINEAGE_TRACING is disabled (standard runs). 63 self.lineage_id = lineage_id 64 self.parent_lineage_id = parent_lineage_id 65 # positions: int32 array of shape (n, 2) holding (q, r) axial hex coordinates 66 # on the toroidal lattice. None when LATTICE_MODE is disabled (standard runs). 67 self.positions = positions 68 69 assert isinstance(phenotypes, Phenotypes) 70 71 if not ( 72 len(genomes) 73 == len(ages) 74 == len(births) 75 == len(birthdays) 76 == len(phenotypes) 77 == len(infection) 78 == len(sizes) 79 == len(sexes) 80 # == len(generations) 81 ): 82 raise ValueError("Population attributes must have equal length") 83 84 def __setstate__(self, state): 85 # Backward compat: pickles saved before ancestry/lineage/positions fields were added 86 self.__dict__.update(state) 87 if "ancestry" not in self.__dict__: 88 self.ancestry = None 89 if "lineage_id" not in self.__dict__: 90 self.lineage_id = None 91 if "parent_lineage_id" not in self.__dict__: 92 self.parent_lineage_id = None 93 if "positions" not in self.__dict__: 94 self.positions = None 95 96 def __len__(self): 97 """Return the number of living individuals.""" 98 return len(self.genomes) 99 100 def __getitem__(self, index): 101 """Return a subpopulation.""" 102 return Population( 103 genomes=self.genomes.get(individuals=index), 104 ages=self.ages[index], 105 births=self.births[index], 106 birthdays=self.birthdays[index], 107 phenotypes=self.phenotypes.get(individuals=index), 108 infection=self.infection[index], 109 sizes=self.sizes[index], 110 sexes=self.sexes[index], 111 generations=self.generations[index] if self.generations is not None else None, 112 ancestry=self.ancestry[index] if self.ancestry is not None else None, 113 lineage_id=self.lineage_id[index] if self.lineage_id is not None else None, 114 parent_lineage_id=self.parent_lineage_id[index] if self.parent_lineage_id is not None else None, 115 positions=self.positions[index] if self.positions is not None else None, 116 ) 117 118 def __imul__(self, index): 119 """Redefine itself as its own subpopulation.""" 120 for attr in self.attrs: 121 if attr == "genomes": 122 self.genomes.keep(individuals=index) 123 elif attr == "phenotypes": 124 self.phenotypes.keep(individuals=index) 125 elif attr == "generations": 126 self.generations = None 127 elif attr == "ancestry": 128 if self.ancestry is not None: 129 self.ancestry = self.ancestry[index] 130 elif attr in ("lineage_id", "parent_lineage_id", "positions"): 131 current = getattr(self, attr) 132 if current is not None: 133 setattr(self, attr, current[index]) 134 else: 135 setattr(self, attr, getattr(self, attr)[index]) 136 return self 137 138 def __iadd__(self, population): 139 """Merge with another population.""" 140 141 for attr in self.attrs: 142 if attr == "genomes": 143 self.genomes.add(population.genomes) 144 elif attr == "phenotypes": 145 assert isinstance(population.phenotypes, Phenotypes) 146 self.phenotypes.add(population.phenotypes) 147 elif attr == "generations": 148 self.generations = None 149 elif attr == "ancestry": 150 if self.ancestry is not None and population.ancestry is not None: 151 self.ancestry = np.concatenate([self.ancestry, population.ancestry]) 152 elif self.ancestry is not None or population.ancestry is not None: 153 # one side has ancestry tracking, the other doesn't — treat missing as all-native 154 a = self.ancestry if self.ancestry is not None else np.zeros(self.genomes.array.shape, dtype=np.bool_) 155 b = population.ancestry if population.ancestry is not None else np.zeros(population.genomes.array.shape, dtype=np.bool_) 156 self.ancestry = np.concatenate([a, b]) 157 elif attr in ("lineage_id", "parent_lineage_id"): 158 self_val = getattr(self, attr) 159 other_val = getattr(population, attr) 160 if self_val is None and other_val is None: 161 continue 162 # If only one side has lineage IDs, treat the missing side as -1 sentinels. 163 # (No lineage tracking on one side means we cannot reconstruct ancestry — keep what we have.) 164 if self_val is None: 165 self_val = np.full(len(self.genomes), -1, dtype=np.int64) 166 if other_val is None: 167 other_val = np.full(len(population.genomes), -1, dtype=np.int64) 168 setattr(self, attr, np.concatenate([self_val, other_val])) 169 elif attr == "positions": 170 self_val = self.positions 171 other_val = population.positions 172 if self_val is None and other_val is None: 173 continue 174 # Merging spatial + non-spatial populations is unusual. If one side has 175 # positions and the other doesn't, sentinel-fill (-1, -1) for the missing side. 176 # Lattice submodel must claim cells for these once the merge is realized. 177 if self_val is None: 178 self_val = np.full((len(self.genomes), 2), -1, dtype=np.int32) 179 if other_val is None: 180 other_val = np.full((len(population.genomes), 2), -1, dtype=np.int32) 181 self.positions = np.concatenate([self_val, other_val]) 182 else: 183 val = np.concatenate([getattr(self, attr), getattr(population, attr)]) 184 setattr(self, attr, val) 185 return self 186 187 # def shuffle(self): 188 # order = np.random.arange(len(self)) 189 # np.random.shuffle(order) 190 # self *= order 191 192 @staticmethod 193 def load_pickle_from(path: pathlib.Path): 194 assert path.exists(), f"pickle_path {path} does not exist" 195 with open(path, "rb") as file_: 196 return pickle.load(file_) 197 198 def save_pickle_to(self, path): 199 with open(path, "wb") as file_: 200 pickle.dump(self, file_) 201 202 @staticmethod 203 def initialize(n, AGE_LIMIT): 204 from aegis_sim.parameterization import parametermanager 205 from aegis_sim import variables 206 207 genomes = Genomes(submodels.architect.architecture.init_genome_array(n)) 208 ages = np.random.randint(low=0, high=AGE_LIMIT, size=n, dtype=np.int32) 209 births = np.zeros(n, dtype=np.int32) 210 birthdays = np.zeros(n, dtype=np.int32) 211 # generations = np.zeros(n, dtype=np.int32) 212 generations = None 213 214 phenotypes = submodels.architect.__call__(genomes) 215 assert isinstance(phenotypes, Phenotypes) 216 217 infection = np.zeros(n, dtype=np.int32) 218 sizes = np.zeros(n, dtype=np.float32) 219 sexes = submodels.sexsystem.get_sex(n) 220 221 if parametermanager.parameters.LINEAGE_TRACING: 222 lineage_id = variables.next_lineage_ids(n) 223 parent_lineage_id = np.full(n, -1, dtype=np.int64) 224 else: 225 lineage_id = None 226 parent_lineage_id = None 227 228 # Spatial-model: when LATTICE_MODE is on, assign each individual a unique cell 229 # on the toroidal hex lattice. The lattice submodel owns the cell-occupancy 230 # bookkeeping; here we just record each individual's (q, r) coords. When 231 # LATTICE_MODE is off (default), positions stays None and behavior is unchanged. 232 if parametermanager.parameters.LATTICE_MODE: 233 positions = submodels.lattice.assign_initial_positions(n) 234 else: 235 positions = None 236 237 return Population( 238 genomes=genomes, 239 ages=ages, 240 births=births, 241 birthdays=birthdays, 242 generations=generations, 243 phenotypes=phenotypes, 244 infection=infection, 245 sizes=sizes, 246 sexes=sexes, 247 ancestry=None, 248 lineage_id=lineage_id, 249 parent_lineage_id=parent_lineage_id, 250 positions=positions, 251 ) 252 253 @staticmethod 254 def make_eggs( 255 offspring_genomes: Genomes, 256 step, 257 offspring_sexes, 258 parental_generations, 259 offspring_ancestry=None, 260 offspring_lineage_id=None, 261 offspring_parent_lineage_id=None, 262 offspring_positions=None, 263 ): 264 n = len(offspring_genomes) 265 eggs = Population( 266 genomes=offspring_genomes, 267 ages=np.zeros(n, dtype=np.int32), 268 births=np.zeros(n, dtype=np.int32), 269 birthdays=np.zeros(n, dtype=np.int32) + step, 270 generations=None, 271 phenotypes=Phenotypes.init_phenotype_array(n), 272 infection=np.zeros(n, dtype=np.int32), 273 sizes=np.zeros(n, dtype=np.float32), 274 sexes=offspring_sexes, 275 ancestry=offspring_ancestry, 276 lineage_id=offspring_lineage_id, 277 parent_lineage_id=offspring_parent_lineage_id, 278 positions=offspring_positions, 279 ) 280 return eggs
Population data
Contains demographic, genetic and phenotypic data of living individuals.
Population( genomes: aegis_sim.dataclasses.genomes.Genomes, ages, births, birthdays, phenotypes: aegis_sim.dataclasses.phenotypes.Phenotypes, infection, sizes, sexes, generations=None, ancestry=None, lineage_id=None, parent_lineage_id=None, positions=None)
33 def __init__( 34 self, 35 genomes: Genomes, 36 ages, 37 births, 38 birthdays, 39 phenotypes: Phenotypes, 40 infection, 41 sizes, 42 sexes, 43 generations=None, 44 ancestry=None, 45 lineage_id=None, 46 parent_lineage_id=None, 47 positions=None, 48 ): 49 self.genomes = genomes 50 self.ages = ages 51 self.births = births 52 self.birthdays = birthdays 53 self.phenotypes = phenotypes 54 self.infection = infection 55 self.sizes = sizes 56 self.sexes = sexes 57 self.generations = generations 58 # ancestry: bool array, same shape as genomes.array; True = introgressed from source pop. 59 # None when introgression tracking is disabled (standard runs). 60 self.ancestry = ancestry 61 # lineage_id, parent_lineage_id: int64 arrays of length len(genomes). 62 # Both None when LINEAGE_TRACING is disabled (standard runs). 63 self.lineage_id = lineage_id 64 self.parent_lineage_id = parent_lineage_id 65 # positions: int32 array of shape (n, 2) holding (q, r) axial hex coordinates 66 # on the toroidal lattice. None when LATTICE_MODE is disabled (standard runs). 67 self.positions = positions 68 69 assert isinstance(phenotypes, Phenotypes) 70 71 if not ( 72 len(genomes) 73 == len(ages) 74 == len(births) 75 == len(birthdays) 76 == len(phenotypes) 77 == len(infection) 78 == len(sizes) 79 == len(sexes) 80 # == len(generations) 81 ): 82 raise ValueError("Population attributes must have equal length")
attrs =
('genomes', 'ages', 'births', 'birthdays', 'generations', 'phenotypes', 'infection', 'sizes', 'sexes', 'ancestry', 'lineage_id', 'parent_lineage_id', 'positions')
@staticmethod
def
initialize(n, AGE_LIMIT):
202 @staticmethod 203 def initialize(n, AGE_LIMIT): 204 from aegis_sim.parameterization import parametermanager 205 from aegis_sim import variables 206 207 genomes = Genomes(submodels.architect.architecture.init_genome_array(n)) 208 ages = np.random.randint(low=0, high=AGE_LIMIT, size=n, dtype=np.int32) 209 births = np.zeros(n, dtype=np.int32) 210 birthdays = np.zeros(n, dtype=np.int32) 211 # generations = np.zeros(n, dtype=np.int32) 212 generations = None 213 214 phenotypes = submodels.architect.__call__(genomes) 215 assert isinstance(phenotypes, Phenotypes) 216 217 infection = np.zeros(n, dtype=np.int32) 218 sizes = np.zeros(n, dtype=np.float32) 219 sexes = submodels.sexsystem.get_sex(n) 220 221 if parametermanager.parameters.LINEAGE_TRACING: 222 lineage_id = variables.next_lineage_ids(n) 223 parent_lineage_id = np.full(n, -1, dtype=np.int64) 224 else: 225 lineage_id = None 226 parent_lineage_id = None 227 228 # Spatial-model: when LATTICE_MODE is on, assign each individual a unique cell 229 # on the toroidal hex lattice. The lattice submodel owns the cell-occupancy 230 # bookkeeping; here we just record each individual's (q, r) coords. When 231 # LATTICE_MODE is off (default), positions stays None and behavior is unchanged. 232 if parametermanager.parameters.LATTICE_MODE: 233 positions = submodels.lattice.assign_initial_positions(n) 234 else: 235 positions = None 236 237 return Population( 238 genomes=genomes, 239 ages=ages, 240 births=births, 241 birthdays=birthdays, 242 generations=generations, 243 phenotypes=phenotypes, 244 infection=infection, 245 sizes=sizes, 246 sexes=sexes, 247 ancestry=None, 248 lineage_id=lineage_id, 249 parent_lineage_id=parent_lineage_id, 250 positions=positions, 251 )
@staticmethod
def
make_eggs( offspring_genomes: aegis_sim.dataclasses.genomes.Genomes, step, offspring_sexes, parental_generations, offspring_ancestry=None, offspring_lineage_id=None, offspring_parent_lineage_id=None, offspring_positions=None):
253 @staticmethod 254 def make_eggs( 255 offspring_genomes: Genomes, 256 step, 257 offspring_sexes, 258 parental_generations, 259 offspring_ancestry=None, 260 offspring_lineage_id=None, 261 offspring_parent_lineage_id=None, 262 offspring_positions=None, 263 ): 264 n = len(offspring_genomes) 265 eggs = Population( 266 genomes=offspring_genomes, 267 ages=np.zeros(n, dtype=np.int32), 268 births=np.zeros(n, dtype=np.int32), 269 birthdays=np.zeros(n, dtype=np.int32) + step, 270 generations=None, 271 phenotypes=Phenotypes.init_phenotype_array(n), 272 infection=np.zeros(n, dtype=np.int32), 273 sizes=np.zeros(n, dtype=np.float32), 274 sexes=offspring_sexes, 275 ancestry=offspring_ancestry, 276 lineage_id=offspring_lineage_id, 277 parent_lineage_id=offspring_parent_lineage_id, 278 positions=offspring_positions, 279 ) 280 return eggs