aegis_sim.submodels.reproduction.matingmanager
1import numpy as np 2 3 4class MatingManager: 5 def __init__(self): 6 pass 7 8 def pair_up_polygamously(self, sexes, parent_positions=None, max_search_radius=0): 9 """ 10 Return slot indices (into the reproducing pool) of paired males and females. 11 12 When `parent_positions` is None: classical well-mixed pairing — males and 13 females are shuffled and paired up to min(n_males, n_females) pairs. 14 15 When `parent_positions` is provided (LATTICE_MODE=True): expanding-ring 16 spatial pairing — for each female, search out from her cell up to 17 `max_search_radius` hex rings; pair her with a random male found in the 18 closest ring that contains any male. Males can mate with multiple 19 females (polygamous), so a single male slot may be paired multiple times. 20 Females who find no male within the search radius are not paired. 21 """ 22 indices_male = (sexes == 0).nonzero()[0] 23 indices_female = (sexes == 1).nonzero()[0] 24 25 if parent_positions is None: 26 # Classical well-mixed pairing (unchanged behaviour) 27 n_pairs = min(len(indices_male), len(indices_female)) 28 np.random.shuffle(indices_male) 29 np.random.shuffle(indices_female) 30 males = indices_male[:n_pairs] 31 females = indices_female[:n_pairs] 32 return males, females 33 34 # Lattice-aware pairing 35 if len(indices_male) == 0 or len(indices_female) == 0: 36 return np.array([], dtype=np.int64), np.array([], dtype=np.int64) 37 38 from aegis_sim.submodels import lattice 39 40 # Build a lookup: (q, r) -> male slot index. If multiple males share a 41 # cell (shouldn't happen with one-per-cell, but defensive), the last wins. 42 male_pos_to_slot = {} 43 for slot in indices_male: 44 q, r = parent_positions[slot] 45 male_pos_to_slot[(int(q), int(r))] = int(slot) 46 47 paired_males = [] 48 paired_females = [] 49 # Shuffle female search order so early females don't monopolise nearby males 50 female_order = indices_female.copy() 51 np.random.shuffle(female_order) 52 53 for f_slot in female_order: 54 f_q, f_r = parent_positions[f_slot] 55 found = -1 56 for radius in range(1, max_search_radius + 1): 57 ring_cells = lattice.ring(int(f_q), int(f_r), radius) 58 candidates = [] 59 for c in ring_cells: 60 key = (int(c[0]), int(c[1])) 61 if key in male_pos_to_slot: 62 candidates.append(male_pos_to_slot[key]) 63 if candidates: 64 found = int(candidates[np.random.randint(len(candidates))]) 65 break 66 if found != -1: 67 paired_males.append(found) 68 paired_females.append(int(f_slot)) 69 70 return ( 71 np.asarray(paired_males, dtype=np.int64), 72 np.asarray(paired_females, dtype=np.int64), 73 ) 74 75 def pair_up_monogamously(self, sexes): 76 return
class
MatingManager:
5class MatingManager: 6 def __init__(self): 7 pass 8 9 def pair_up_polygamously(self, sexes, parent_positions=None, max_search_radius=0): 10 """ 11 Return slot indices (into the reproducing pool) of paired males and females. 12 13 When `parent_positions` is None: classical well-mixed pairing — males and 14 females are shuffled and paired up to min(n_males, n_females) pairs. 15 16 When `parent_positions` is provided (LATTICE_MODE=True): expanding-ring 17 spatial pairing — for each female, search out from her cell up to 18 `max_search_radius` hex rings; pair her with a random male found in the 19 closest ring that contains any male. Males can mate with multiple 20 females (polygamous), so a single male slot may be paired multiple times. 21 Females who find no male within the search radius are not paired. 22 """ 23 indices_male = (sexes == 0).nonzero()[0] 24 indices_female = (sexes == 1).nonzero()[0] 25 26 if parent_positions is None: 27 # Classical well-mixed pairing (unchanged behaviour) 28 n_pairs = min(len(indices_male), len(indices_female)) 29 np.random.shuffle(indices_male) 30 np.random.shuffle(indices_female) 31 males = indices_male[:n_pairs] 32 females = indices_female[:n_pairs] 33 return males, females 34 35 # Lattice-aware pairing 36 if len(indices_male) == 0 or len(indices_female) == 0: 37 return np.array([], dtype=np.int64), np.array([], dtype=np.int64) 38 39 from aegis_sim.submodels import lattice 40 41 # Build a lookup: (q, r) -> male slot index. If multiple males share a 42 # cell (shouldn't happen with one-per-cell, but defensive), the last wins. 43 male_pos_to_slot = {} 44 for slot in indices_male: 45 q, r = parent_positions[slot] 46 male_pos_to_slot[(int(q), int(r))] = int(slot) 47 48 paired_males = [] 49 paired_females = [] 50 # Shuffle female search order so early females don't monopolise nearby males 51 female_order = indices_female.copy() 52 np.random.shuffle(female_order) 53 54 for f_slot in female_order: 55 f_q, f_r = parent_positions[f_slot] 56 found = -1 57 for radius in range(1, max_search_radius + 1): 58 ring_cells = lattice.ring(int(f_q), int(f_r), radius) 59 candidates = [] 60 for c in ring_cells: 61 key = (int(c[0]), int(c[1])) 62 if key in male_pos_to_slot: 63 candidates.append(male_pos_to_slot[key]) 64 if candidates: 65 found = int(candidates[np.random.randint(len(candidates))]) 66 break 67 if found != -1: 68 paired_males.append(found) 69 paired_females.append(int(f_slot)) 70 71 return ( 72 np.asarray(paired_males, dtype=np.int64), 73 np.asarray(paired_females, dtype=np.int64), 74 ) 75 76 def pair_up_monogamously(self, sexes): 77 return
def
pair_up_polygamously(self, sexes, parent_positions=None, max_search_radius=0):
9 def pair_up_polygamously(self, sexes, parent_positions=None, max_search_radius=0): 10 """ 11 Return slot indices (into the reproducing pool) of paired males and females. 12 13 When `parent_positions` is None: classical well-mixed pairing — males and 14 females are shuffled and paired up to min(n_males, n_females) pairs. 15 16 When `parent_positions` is provided (LATTICE_MODE=True): expanding-ring 17 spatial pairing — for each female, search out from her cell up to 18 `max_search_radius` hex rings; pair her with a random male found in the 19 closest ring that contains any male. Males can mate with multiple 20 females (polygamous), so a single male slot may be paired multiple times. 21 Females who find no male within the search radius are not paired. 22 """ 23 indices_male = (sexes == 0).nonzero()[0] 24 indices_female = (sexes == 1).nonzero()[0] 25 26 if parent_positions is None: 27 # Classical well-mixed pairing (unchanged behaviour) 28 n_pairs = min(len(indices_male), len(indices_female)) 29 np.random.shuffle(indices_male) 30 np.random.shuffle(indices_female) 31 males = indices_male[:n_pairs] 32 females = indices_female[:n_pairs] 33 return males, females 34 35 # Lattice-aware pairing 36 if len(indices_male) == 0 or len(indices_female) == 0: 37 return np.array([], dtype=np.int64), np.array([], dtype=np.int64) 38 39 from aegis_sim.submodels import lattice 40 41 # Build a lookup: (q, r) -> male slot index. If multiple males share a 42 # cell (shouldn't happen with one-per-cell, but defensive), the last wins. 43 male_pos_to_slot = {} 44 for slot in indices_male: 45 q, r = parent_positions[slot] 46 male_pos_to_slot[(int(q), int(r))] = int(slot) 47 48 paired_males = [] 49 paired_females = [] 50 # Shuffle female search order so early females don't monopolise nearby males 51 female_order = indices_female.copy() 52 np.random.shuffle(female_order) 53 54 for f_slot in female_order: 55 f_q, f_r = parent_positions[f_slot] 56 found = -1 57 for radius in range(1, max_search_radius + 1): 58 ring_cells = lattice.ring(int(f_q), int(f_r), radius) 59 candidates = [] 60 for c in ring_cells: 61 key = (int(c[0]), int(c[1])) 62 if key in male_pos_to_slot: 63 candidates.append(male_pos_to_slot[key]) 64 if candidates: 65 found = int(candidates[np.random.randint(len(candidates))]) 66 break 67 if found != -1: 68 paired_males.append(found) 69 paired_females.append(int(f_slot)) 70 71 return ( 72 np.asarray(paired_males, dtype=np.int64), 73 np.asarray(paired_females, dtype=np.int64), 74 )
Return slot indices (into the reproducing pool) of paired males and females.
When parent_positions is None: classical well-mixed pairing — males and
females are shuffled and paired up to min(n_males, n_females) pairs.
When parent_positions is provided (LATTICE_MODE=True): expanding-ring
spatial pairing — for each female, search out from her cell up to
max_search_radius hex rings; pair her with a random male found in the
closest ring that contains any male. Males can mate with multiple
females (polygamous), so a single male slot may be paired multiple times.
Females who find no male within the search radius are not paired.