def apply_preset_fitness_patch(deps: FitnessPopulationView, patch: PresetFitnessPatch) -> None:
"""Apply a declarative preset fitness patch to population config tensors.
Patch schema (all keys optional):
- viability: Dict[genotype_selector, _ViabilityScalingConfig]
- fecundity: Dict[genotype_selector, _FecundityScalingConfig]
- sexual_selection: Dict[female_selector, Union[float, Dict[male_selector, float]]]
"""
if not patch:
return
all_genotypes = deps.index_registry.index_to_genotype
# ----------------------------------------------------------------------
# 1) Selector-based viability patch
#
# Input examples:
# - {"A1|A1": 0.8}
# - {"A1|A1": {0: 0.9, 1: 0.8}}
# - {"A1|A1": {"female": 0.9, "male": {0: 0.95}}}
# ----------------------------------------------------------------------
viability_patch = patch.get('viability', {})
for selector, config in viability_patch.items():
matched = deps.species.resolve_genotype_selectors(
selector=selector,
all_genotypes=all_genotypes,
context='preset.viability',
)
for genotype in matched:
z_indices = deps.index_registry.ztype_indices_for(genotype)
# scalar: both sexes at default viability age
if isinstance(config, (int, float)):
for z_idx in z_indices:
deps.config.set_viability_fitness(0, z_idx, float(config))
deps.config.set_viability_fitness(1, z_idx, float(config))
continue
# age-specific for both sexes: {age: scale}
config_map = cast(Mapping[object, object], config)
if _is_simple_age_scale_map(config_map):
for z_idx in z_indices:
for age, scale in config_map.items():
deps.config.set_viability_fitness(0, z_idx, float(scale), int(age))
deps.config.set_viability_fitness(1, z_idx, float(scale), int(age))
continue
# sex-specific: {sex: float | {age: scale}}
for sex_key, sex_config in config_map.items():
sex_idx = _normalize_sex_key(_coerce_sex_specifier(sex_key))
if isinstance(sex_config, (int, float)):
for z_idx in z_indices:
deps.config.set_viability_fitness(sex_idx, z_idx, float(sex_config))
elif isinstance(sex_config, Mapping):
sex_age_map = cast(Mapping[object, object], sex_config)
for z_idx in z_indices:
for age, scale in sex_age_map.items():
if not isinstance(age, int) or not isinstance(scale, (int, float)):
raise TypeError(
f"Invalid viability sex-age config for selector '{selector}', sex '{sex_key}'"
)
deps.config.set_viability_fitness(sex_idx, z_idx, float(scale), int(age))
else:
raise TypeError(
f"Invalid viability sex config for selector '{selector}', sex '{sex_key}': "
f"{type(sex_config).__name__}"
)
# ----------------------------------------------------------------------
# 2) Selector-based fecundity patch
#
# Input examples:
# - {"A1|A1": 0.8}
# - {"A1|A1": {"female": 0.9, "male": 0.7}}
# ----------------------------------------------------------------------
fecundity_patch = patch.get('fecundity', {})
for selector, config in fecundity_patch.items():
matched = deps.species.resolve_genotype_selectors(
selector=selector,
all_genotypes=all_genotypes,
context='preset.fecundity',
)
for genotype in matched:
z_indices = deps.index_registry.ztype_indices_for(genotype)
if isinstance(config, (int, float)):
for z_idx in z_indices:
deps.config.set_fecundity_fitness(0, z_idx, float(config))
deps.config.set_fecundity_fitness(1, z_idx, float(config))
continue
config_map = cast(Mapping[object, object], config)
for sex_key, scale in config_map.items():
sex_idx = _normalize_sex_key(_coerce_sex_specifier(sex_key))
if not isinstance(scale, (int, float)):
raise TypeError(
f"Invalid fecundity sex scale for selector '{selector}', sex '{sex_key}'"
)
for z_idx in z_indices:
deps.config.set_fecundity_fitness(sex_idx, z_idx, float(scale))
# ----------------------------------------------------------------------
# 3) Selector-based sexual-selection patch
#
# Input examples:
# - {"female_selector": 0.9} # shorthand for all males
# - {"female_selector": {"male_selector": 1.2}}
# ----------------------------------------------------------------------
sexual_selection_patch = patch.get('sexual_selection', {})
for female_selector, male_config in sexual_selection_patch.items():
female_matched = deps.species.resolve_genotype_selectors(
selector=female_selector,
all_genotypes=all_genotypes,
context='preset.sexual_selection(female)',
)
# Allow shorthand: female_selector -> scalar means all-male targets
if isinstance(male_config, (int, float)):
male_map = {'*': float(male_config)}
else:
male_map = cast(Mapping[object, object], male_config)
for male_selector, scale in male_map.items():
if not isinstance(scale, (int, float)):
raise TypeError(
f"Invalid sexual_selection scale for female selector '{female_selector}'"
)
male_matched = deps.species.resolve_genotype_selectors(
selector=_coerce_selector(male_selector),
all_genotypes=all_genotypes,
context='preset.sexual_selection(male)',
)
for f_genotype in female_matched:
f_z_indices = deps.index_registry.ztype_indices_for(f_genotype)
for m_genotype in male_matched:
m_z_indices = deps.index_registry.ztype_indices_for(m_genotype)
for f_z in f_z_indices:
for m_z in m_z_indices:
deps.config.set_sexual_selection_fitness(f_z, m_z, float(scale))
# ----------------------------------------------------------------------
# 4) Allele-based patches
#
# This layer expands allele-centric config into genotype-level writes:
# 1) resolve allele name(s) to Gene objects
# 2) count target copies per genotype (0/1/2)
# 3) convert copies -> factor according to mode
# 4) multiply corresponding tensor cells
# ----------------------------------------------------------------------
viability_per_allele_patch = patch.get('viability_per_allele', {})
for allele_name, val in viability_per_allele_patch.items():
config, mode = _split_config_mode(val)
if not _is_viability_scaling_config(config):
raise TypeError(f"Invalid viability_per_allele config for '{allele_name}'")
_apply_viability_allele_scaling(deps, all_genotypes, allele_name, config, mode)
fecundity_per_allele_patch = patch.get('fecundity_per_allele', {})
for allele_name, val in fecundity_per_allele_patch.items():
config, mode = _split_config_mode(val)
if not _is_fecundity_scaling_config(config):
raise TypeError(f"Invalid fecundity_per_allele config for '{allele_name}'")
_apply_fecundity_allele_scaling(deps, all_genotypes, allele_name, config, mode)
sexual_selection_per_allele_patch = patch.get('sexual_selection_per_allele', {})
for allele_name, val in sexual_selection_per_allele_patch.items():
config, mode = _split_config_mode(val)
if not _is_sexual_selection_scaling_config(config):
raise TypeError(f"Invalid sexual_selection_per_allele config for '{allele_name}'")
_apply_sexual_selection_allele_scaling(deps, all_genotypes, allele_name, config, mode)
# 5) Zygote fitness patch
zygote_patch = patch.get('zygote', {})
for selector, config in zygote_patch.items():
matched = deps.species.resolve_genotype_selectors(
selector=selector,
all_genotypes=all_genotypes,
context='preset.zygote',
)
for genotype in matched:
z_indices = deps.index_registry.ztype_indices_for(genotype)
if isinstance(config, (int, float)):
for z_idx in z_indices:
deps.config.set_zygote_viability_fitness(0, z_idx, float(config))
deps.config.set_zygote_viability_fitness(1, z_idx, float(config))
elif isinstance(config, Mapping):
config_map = cast(Mapping[object, object], config)
for sex_key, sex_config in config_map.items():
sex_idx = _normalize_sex_key(_coerce_sex_specifier(sex_key))
if isinstance(sex_config, (int, float)):
for z_idx in z_indices:
deps.config.set_zygote_viability_fitness(sex_idx, z_idx, float(sex_config))
# 6) Zygote allele-based fitness patch
zygote_per_allele_patch = patch.get('zygote_per_allele', {})
for allele_name, val in zygote_per_allele_patch.items():
config, mode = _split_config_mode(val)
if not _is_zygote_viability_scaling_config(config):
raise TypeError(f"Invalid zygote_per_allele config for '{allele_name}'")
_apply_zygote_viability_allele_scaling(deps, all_genotypes, allele_name, config, mode)
# 7) Slab-based fitness patches (per_slab keys — symmetric with per_allele)
if patch.get('viability_per_slab'):
_apply_viability_slab_scaling(deps, all_genotypes, patch)
if patch.get('fecundity_per_slab'):
_apply_fecundity_slab_scaling(deps, all_genotypes, patch)
if patch.get('sexual_selection_per_slab'):
_apply_sexual_selection_slab_scaling(deps, all_genotypes, patch)
if patch.get('zygote_per_slab'):
_apply_zygote_slab_scaling(deps, all_genotypes, patch)