aegis_sim.submodels.reproduction.pairing

 1import numpy as np
 2from numba import njit, prange
 3from aegis_sim import variables
 4from aegis_sim.dataclasses.genomes import Genomes
 5from aegis_sim import submodels
 6
 7
 8@njit(parallel=True)
 9def _assemble_children(genome_array, males, females, male_gamete_idx, female_gamete_idx):
10    """Assemble children genomes directly from parent genome array.
11
12    Reads each parent's selected chromatid and writes it into the children array
13    in one pass, parallelized over pairs. No intermediate arrays allocated.
14    """
15    n_pairs = len(males)
16    # genome_array shape: (n_individuals, ploidy, loci, bpl)
17    n_loci = genome_array.shape[2]
18    n_bpl = genome_array.shape[3]
19    children = np.empty((n_pairs, 2, n_loci, n_bpl), dtype=genome_array.dtype)
20
21    for p in prange(n_pairs):
22        m = males[p]
23        f = females[p]
24        mg = male_gamete_idx[p]
25        fg = female_gamete_idx[p]
26        for i in range(n_loci):
27            for j in range(n_bpl):
28                children[p, 0, i, j] = genome_array[m, mg, i, j]
29                children[p, 1, i, j] = genome_array[f, fg, i, j]
30
31    return children
32
33
34def pairing(genomes: Genomes, parental_sexes, ages, muta_prob, ancestry=None,
35            parent_positions=None, max_search_radius=0):
36    """Return assorted chromatids.
37
38    When `parent_positions` is given (LATTICE_MODE), the mating manager does
39    lattice-aware expanding-ring pairing. Females who can't find a male within
40    `max_search_radius` rings are not paired. Otherwise (default), classical
41    well-mixed pairing happens.
42
43    Returns `(children, child_ages, child_muta_prob, [child_ancestry], female_slots)`
44    where `female_slots` are the slot indices (into the reproducing pool) of the
45    paired mothers — the caller uses these to look up mother positions when
46    placing offspring on the lattice.
47    """
48
49    # Get pairs
50    males, females = submodels.matingmanager.pair_up_polygamously(
51        parental_sexes,
52        parent_positions=parent_positions,
53        max_search_radius=max_search_radius,
54    )
55    assert len(males) == len(females)
56    n_pairs = len(males)
57
58    if n_pairs == 0:
59        gshape = genomes.shape()
60        children = np.empty(shape=(0, *gshape[1:]), dtype=np.bool_)
61        empty_female_slots = np.empty(0, dtype=np.int64)
62        if ancestry is not None:
63            empty_ancestry = np.empty(shape=(0, *ancestry.shape[1:]), dtype=ancestry.dtype)
64            return children, ages[females], muta_prob[females], empty_ancestry, empty_female_slots
65        return children, ages[females], muta_prob[females], empty_female_slots
66
67    # Random gamete selection (chromatid 0 or 1 per parent)
68    male_gamete_idx = (variables.rng.random(n_pairs) < 0.5).astype(np.int32)
69    female_gamete_idx = (variables.rng.random(n_pairs) < 0.5).astype(np.int32)
70
71    # Assemble children directly from genome array — no intermediate copies
72    children = _assemble_children(
73        genomes.array, males, females, male_gamete_idx, female_gamete_idx,
74    )
75
76    if ancestry is not None:
77        offspring_ancestry = np.empty((n_pairs, *ancestry.shape[1:]), dtype=ancestry.dtype)
78        offspring_ancestry[:, 0] = ancestry[males, male_gamete_idx]
79        offspring_ancestry[:, 1] = ancestry[females, female_gamete_idx]
80        # TODO fix splitting of ages and muta_prob
81        return children, ages[females], muta_prob[females], offspring_ancestry, females
82
83    # TODO fix splitting of ages and muta_prob
84    return children, ages[females], muta_prob[females], females
def pairing( genomes: aegis_sim.dataclasses.genomes.Genomes, parental_sexes, ages, muta_prob, ancestry=None, parent_positions=None, max_search_radius=0):
35def pairing(genomes: Genomes, parental_sexes, ages, muta_prob, ancestry=None,
36            parent_positions=None, max_search_radius=0):
37    """Return assorted chromatids.
38
39    When `parent_positions` is given (LATTICE_MODE), the mating manager does
40    lattice-aware expanding-ring pairing. Females who can't find a male within
41    `max_search_radius` rings are not paired. Otherwise (default), classical
42    well-mixed pairing happens.
43
44    Returns `(children, child_ages, child_muta_prob, [child_ancestry], female_slots)`
45    where `female_slots` are the slot indices (into the reproducing pool) of the
46    paired mothers — the caller uses these to look up mother positions when
47    placing offspring on the lattice.
48    """
49
50    # Get pairs
51    males, females = submodels.matingmanager.pair_up_polygamously(
52        parental_sexes,
53        parent_positions=parent_positions,
54        max_search_radius=max_search_radius,
55    )
56    assert len(males) == len(females)
57    n_pairs = len(males)
58
59    if n_pairs == 0:
60        gshape = genomes.shape()
61        children = np.empty(shape=(0, *gshape[1:]), dtype=np.bool_)
62        empty_female_slots = np.empty(0, dtype=np.int64)
63        if ancestry is not None:
64            empty_ancestry = np.empty(shape=(0, *ancestry.shape[1:]), dtype=ancestry.dtype)
65            return children, ages[females], muta_prob[females], empty_ancestry, empty_female_slots
66        return children, ages[females], muta_prob[females], empty_female_slots
67
68    # Random gamete selection (chromatid 0 or 1 per parent)
69    male_gamete_idx = (variables.rng.random(n_pairs) < 0.5).astype(np.int32)
70    female_gamete_idx = (variables.rng.random(n_pairs) < 0.5).astype(np.int32)
71
72    # Assemble children directly from genome array — no intermediate copies
73    children = _assemble_children(
74        genomes.array, males, females, male_gamete_idx, female_gamete_idx,
75    )
76
77    if ancestry is not None:
78        offspring_ancestry = np.empty((n_pairs, *ancestry.shape[1:]), dtype=ancestry.dtype)
79        offspring_ancestry[:, 0] = ancestry[males, male_gamete_idx]
80        offspring_ancestry[:, 1] = ancestry[females, female_gamete_idx]
81        # TODO fix splitting of ages and muta_prob
82        return children, ages[females], muta_prob[females], offspring_ancestry, females
83
84    # TODO fix splitting of ages and muta_prob
85    return children, ages[females], muta_prob[females], females

Return assorted chromatids.

When parent_positions is given (LATTICE_MODE), the mating manager does lattice-aware expanding-ring pairing. Females who can't find a male within max_search_radius rings are not paired. Otherwise (default), classical well-mixed pairing happens.

Returns (children, child_ages, child_muta_prob, [child_ancestry], female_slots) where female_slots are the slot indices (into the reproducing pool) of the paired mothers — the caller uses these to look up mother positions when placing offspring on the lattice.