diff --git a/README.md b/README.md index fcb7e7df10..0e66921854 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Flake8](https://img.shields.io/badge/Flake8-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Pytest](https://img.shields.io/badge/Pytest-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Coverage](https://img.shields.io/badge/Coverage-99%25-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Mypy](https://img.shields.io/badge/Mypy-1212%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1239%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems diff --git a/RUFAS/biophysical/animal/animal.py b/RUFAS/biophysical/animal/animal.py index efb1b2a278..6597a91f10 100644 --- a/RUFAS/biophysical/animal/animal.py +++ b/RUFAS/biophysical/animal/animal.py @@ -1,18 +1,21 @@ import sys +from datetime import timedelta from random import random -from typing import Any, Callable +from typing import Callable, cast from scipy.stats import truncnorm from numpy import sqrt from RUFAS.biophysical.animal import animal_constants from RUFAS.biophysical.animal.animal_config import AnimalConfig +from RUFAS.biophysical.animal.animal_genetics.animal_genetics import Genetics from RUFAS.biophysical.animal.animal_module_constants import AnimalModuleConstants from RUFAS.biophysical.animal.data_types.animal_enums import Breed, Sex, AnimalStatus from RUFAS.biophysical.animal.data_types.animal_events import AnimalEvents from RUFAS.biophysical.animal.data_types.body_weight_history import BodyWeightHistory from RUFAS.biophysical.animal.data_types.daily_routines_output import DailyRoutinesOutput from RUFAS.biophysical.animal.data_types.digestive_system import DigestiveSystemInputs +from RUFAS.biophysical.animal.data_types.genetic_history import GeneticHistory from RUFAS.biophysical.animal.data_types.growth import GrowthInputs, GrowthOutputs from RUFAS.biophysical.animal.data_types.milk_production import ( MilkProductionInputs, @@ -85,8 +88,10 @@ class Animal: The mature body weight of the animal, (kg). wean_weight: float The body weight of the animal at weaning, (kg). - net_merit: float - The net merit value of the animal, ($USD). + genetics: Genetics + The genetic attributes of the animal. + genetic_history: list[GeneticHistory] + The genetic history of the animal. body_condition_score_5: float The body condition score on a scale of 1 to 5, (unitless). cull_reason: str @@ -151,7 +156,7 @@ def __init__( | HeiferIIIValuesTypedDict | CowValuesTypedDict ), - simulation_day: int = 0, + time: RufasTime, ) -> None: """ Initializes an Animal object. @@ -168,7 +173,7 @@ def __init__( The dictionary that contains the configuration to initialize an Animal object. """ - initialize_animal_methods = { + initialize_animal_methods: dict[AnimalType, Callable[..., None]] = { AnimalType.CALF: self._initialize_calf_or_heiferI, AnimalType.HEIFER_I: self._initialize_calf_or_heiferI, AnimalType.HEIFER_II: self._initialize_heiferII_or_heiferIII, @@ -181,12 +186,12 @@ def __init__( self.animal_type = AnimalType(args.get("animal_type")) self.days_born = int(args.get("days_born")) self.birth_weight = float(args.get("birth_weight")) - self.net_merit = args.get("net_merit", 0.0) self.body_condition_score_5 = AnimalModuleConstants.DEFAULT_BODY_CONDITION_SCORE_5 self.cull_reason = "" self.body_weight_history: list[BodyWeightHistory] = [] self.pen_history: list[PenHistory] = [] + self.genetic_history: list[GeneticHistory] = [] self.sold_at_day: int | None = None self.stillborn_day: int | None = None self.dead_at_day: int | None = None @@ -211,10 +216,36 @@ def __init__( self._daily_vertical_distance: float = 0.0 self._daily_distance: float = 0.0 - if self.animal_type == AnimalType.CALF and "body_weight" not in args.keys(): - self._initialize_newborn_calf(args, simulation_day) + is_newborn_calf = self.animal_type == AnimalType.CALF and "body_weight" not in args.keys() + if is_newborn_calf: + newborn_args = cast(NewBornCalfValuesTypedDict, args) + self._initialize_newborn_calf(newborn_args, time.simulation_day) + dam_tbv_fat, dam_tbv_protein = newborn_args.get("dam_tbv_fat"), newborn_args.get("dam_tbv_protein") + if dam_tbv_fat and dam_tbv_protein: + self.genetics = Genetics( + birth_year=time.current_date.year, + birth_month=time.current_date.month, + animal_type=AnimalType.CALF, + initialize_new_born_calf=True, + dam_tbv_fat=dam_tbv_fat, + dam_tbv_protein=dam_tbv_protein, + ) + else: + self.genetics = Genetics( + birth_year=time.current_date.year, + animal_type=AnimalType.CALF, + initialize_new_born_calf=False, + parity=self.calves, + ) else: initialize_animal_methods[self.animal_type](args) + self.genetics = Genetics( + birth_year=(time.current_date - timedelta(days=self.days_born)).year, + animal_type=self.animal_type, + initialize_new_born_calf=False, + parity=self.calves, + ) + self.update_genetic_history(simulation_day=time.simulation_day) @classmethod def set_nutrient_standard(cls, nutrient_standard: NutrientStandard) -> None: @@ -1091,6 +1122,20 @@ def milk_statistics(self) -> MilkProductionStatistics: milk_fat=self.milk_production.fat_content, milk_lactose=self.milk_production.lactose_content, parity=self.calves, + days_born=self.days_born, + days_in_pregnancy=self.days_in_pregnancy, + animal_type=self.animal_type, + TBV_fat=self.genetics.TBV_fat, + TBV_protein=self.genetics.TBV_protein, + E_permanent_fat=self.genetics.E_permanent_fat, + E_permanent_protein=self.genetics.E_permanent_protein, + E_temporary_fat=self.genetics.E_temporary_fat, + E_temporary_protein=self.genetics.E_temporary_protein, + phenotype_fat=self.genetics.phenotype_fat, + phenotype_protein=self.genetics.phenotype_protein, + EBV_fat=self.genetics.EBV_fat, + EBV_protein=self.genetics.EBV_protein, + ranking_index=self.genetics.ranking_index, ) def _assign_sex_to_newborn_calf(self) -> None: @@ -1508,7 +1553,8 @@ def daily_reproduction_update( days_born=self.days_born, days_in_pregnancy=self.days_in_pregnancy, days_in_milk=self.days_in_milk, - net_merit=self.net_merit, + dam_tbv_fat=self.genetics.TBV_fat, + dam_tbv_protein=self.genetics.TBV_protein, phosphorus_for_gestation_required_for_calf=self.nutrients.phosphorus_for_gestation_required_for_calf, ) reproduction_outputs: ReproductionOutputs = self.reproduction.reproduction_update(reproduction_inputs, time) @@ -1920,7 +1966,19 @@ def get_animal_values( If the animal_type is not present in the mapping dictionary. """ - mapping: dict[AnimalType, Callable[[], Any]] = { + mapping: dict[ + AnimalType, + Callable[ + [], + ( + CalfValuesTypedDict + | HeiferIValuesTypedDict + | HeiferIIValuesTypedDict + | HeiferIIIValuesTypedDict + | CowValuesTypedDict + ), + ], + ] = { AnimalType.CALF: self._get_calf_values, AnimalType.HEIFER_I: self._get_heiferI_values, AnimalType.HEIFER_II: self._get_heiferII_values, @@ -1950,7 +2008,6 @@ def _get_calf_values(self) -> CalfValuesTypedDict: wean_weight=self.wean_weight, mature_body_weight=self.mature_body_weight, events=str(self.events), - net_merit=self.net_merit, ) def _get_heiferI_values(self) -> HeiferIValuesTypedDict: @@ -1973,7 +2030,6 @@ def _get_heiferI_values(self) -> HeiferIValuesTypedDict: wean_weight=self.wean_weight, mature_body_weight=self.mature_body_weight, events=str(self.events), - net_merit=self.net_merit, ) def _get_heiferII_values(self) -> HeiferIIValuesTypedDict: @@ -1996,7 +2052,6 @@ def _get_heiferII_values(self) -> HeiferIIValuesTypedDict: wean_weight=self.wean_weight, mature_body_weight=self.mature_body_weight, events=str(self.events), - net_merit=self.net_merit, heifer_reproduction_program=self.heifer_reproduction_program.value, heifer_reproduction_sub_protocol=self.heifer_reproduction_sub_program.value, estrus_count=self.reproduction.reproduction_statistics.estrus_count, @@ -2030,7 +2085,6 @@ def _get_heiferIII_values(self) -> HeiferIIIValuesTypedDict: wean_weight=self.wean_weight, mature_body_weight=self.mature_body_weight, events=str(self.events), - net_merit=self.net_merit, heifer_reproduction_program=self.heifer_reproduction_program.value, heifer_reproduction_sub_protocol=self.heifer_reproduction_sub_program.value, estrus_count=self.reproduction.reproduction_statistics.estrus_count, @@ -2064,7 +2118,6 @@ def _get_cow_values(self) -> CowValuesTypedDict: wean_weight=self.wean_weight, mature_body_weight=self.mature_body_weight, events=str(self.events), - net_merit=self.net_merit, calf_birth_weight=self.calf_birth_weight, heifer_reproduction_program=self.heifer_reproduction_program.value, heifer_reproduction_sub_protocol=self.heifer_reproduction_sub_program.value, @@ -2367,3 +2420,38 @@ def calculate_nutrition_requirements( ) return requirements + + def update_genetic_history(self, simulation_day: int) -> None: + if simulation_day not in [data_point["simulation_day"] for data_point in self.genetic_history]: + self.genetic_history.append( + GeneticHistory( + simulation_day=simulation_day, + id=self.id, + days_born=self.days_born, + animal_type=self.animal_type, + days_in_milk=self.days_in_milk, + days_in_pregnancy=self.days_in_pregnancy, + parity=self.calves, + TBV_fat=self.genetics.TBV_fat, + TBV_protein=self.genetics.TBV_protein, + E_permanent_fat=self.genetics.E_permanent_fat, + E_permanent_protein=self.genetics.E_permanent_protein, + E_temporary_fat=self.genetics.E_temporary_fat, + E_temporary_protein=self.genetics.E_temporary_protein, + phenotype_fat=self.genetics.phenotype_fat, + phenotype_protein=self.genetics.phenotype_protein, + EBV_fat=self.genetics.EBV_fat, + EBV_protein=self.genetics.EBV_protein, + ranking_index=self.genetics.ranking_index, + ) + ) + else: + om = OutputManager() + om.add_warning( + "Duplicate Genetic History Entry", + f"Animal {self.id} already has a genetic history entry on day {simulation_day}.", + { + "class": Animal.__name__, + "function": Animal.update_genetic_history.__name__, + }, + ) diff --git a/RUFAS/biophysical/animal/animal_config.py b/RUFAS/biophysical/animal/animal_config.py index 4f73adecdf..c4d1a0ed53 100644 --- a/RUFAS/biophysical/animal/animal_config.py +++ b/RUFAS/biophysical/animal/animal_config.py @@ -376,6 +376,9 @@ class AnimalConfig: milk_reduction_maximum: float + average_phenotype: dict[str, dict[int, float]] = {} + top_listing_semen: dict[str, dict[str, float]] = {} + @classmethod def initialize_animal_config(cls) -> None: """Initialize the animal config from the input manager user input data.""" @@ -531,3 +534,17 @@ def initialize_animal_config(cls) -> None: "function": "initialize_animal_config", }, ) + + average_phenotype = im.get_data("animal_mean_phenotype") + cls.average_phenotype = { + trait: dict(zip(average_phenotype["birth_year"], values)) + for trait, values in average_phenotype.items() + if trait != "birth_year" + } + + top_listing_semen = im.get_data("animal_top_listing_semen") + cls.top_listing_semen = { + trait: dict(zip(top_listing_semen["year_month"], values)) + for trait, values in top_listing_semen.items() + if trait != "year_month" + } diff --git a/RUFAS/biophysical/animal/animal_genetics/animal_genetics.py b/RUFAS/biophysical/animal/animal_genetics/animal_genetics.py index c4511d1b61..8363604eb6 100644 --- a/RUFAS/biophysical/animal/animal_genetics/animal_genetics.py +++ b/RUFAS/biophysical/animal/animal_genetics/animal_genetics.py @@ -1,316 +1,262 @@ -from numpy import sqrt -from datetime import date +import numpy as np +from RUFAS.units import MeasurementUnits -from RUFAS.biophysical.animal.data_types.animal_enums import Breed -from RUFAS.input_manager import InputManager -from RUFAS.rufas_time import RufasTime -from RUFAS.output_manager import OutputManager +from RUFAS.biophysical.animal.animal_config import AnimalConfig +from RUFAS.biophysical.animal.data_types.animal_types import AnimalType from RUFAS.util import Utility -""" -The base change time and value in the net merit value. - -References ----------- -CDCB website - -""" -BASE_CHANGE_LOOKUP_TABLE = { - date.fromisoformat("2020-04-01"): 231, - date.fromisoformat("2014-12-01"): 184, - date.fromisoformat("2010-01-01"): 132, - date.fromisoformat("2005-01-01"): 0, +TBV_FAT_STD = 25.8 +TBV_PROTEIN_STD = 13.4 +TBV_CORRELATION = 0.59 +E_PERMANENT_FAT_STD = 38.8 +E_PERMANENT_PROTEIN_STD = 20.1 +E_PERMANENT_CORRELATION = 0.95 +E_TEMPORARY_FAT_STD = 38.8 +E_TEMPORARY_PROTEIN_STD = 20.1 +E_TEMPORARY_CORRELATION = 0.95 +FAT_ACCURACY_BY_PARITY = {0: 0.75, 1: 0.80, 2: 0.85, 3: 0.90} +PROTEIN_ACCURACY_BY_PARITY = {0: 0.75, 1: 0.80, 2: 0.85, 3: 0.90} + +UNITS = { + "TBV_fat": MeasurementUnits.KILOGRAMS, + "TBV_protein": MeasurementUnits.KILOGRAMS, + "E_permanent_fat": MeasurementUnits.KILOGRAMS, + "E_permanent_protein": MeasurementUnits.KILOGRAMS, + "E_temporary_fat": MeasurementUnits.KILOGRAMS, + "E_temporary_protein": MeasurementUnits.KILOGRAMS, + "phenotype_fat": MeasurementUnits.KILOGRAMS, + "phenotype_protein": MeasurementUnits.KILOGRAMS, + "EBV_fat": MeasurementUnits.KILOGRAMS, + "EBV_protein": MeasurementUnits.KILOGRAMS, + "ranking_index": MeasurementUnits.UNITLESS, } -"""The only breed currently supported in this module is Holsteins.""" -SUPPORTED_BREED: str = "HO" - - -class AnimalGenetics: +class Genetics: """ + Genetic attributes of an animal. + Attributes ---------- - net_merit : dict[str, dict[str, dict[str, float]]], default {} - Lookup table of Net Merit averages and standard deviations (lifetime USD), separated by breed and time. - top_semen : dict[str, dict[str, float]], default {} - Lookup table of Top Listing Semen estimated Predicted Transmitting Ability, separated by breed. - year_month_of_first_net_merit_value: str - The earliest net merit value available in year and month (YYYY-MM) format. - year_month_of_last_net_merit_value: str - The latest net merit value available in year and month (YYYY-MM) format. - year_month_of_first_top_semen_value: str - The earliest top semen value available in year and month (YYYY-MM) format. - year_month_of_last_top_semen_value: str - The latest top semen value available in year and month (YYYY-MM) format. - + TBV_fat : float + True Breeding Value for fat, (kg). + TBV_protein : float + True Breeding Value for protein, (kg). + E_permanent_fat : float + Permanent Environment Effect for fat, (kg). + E_permanent_protein : float + Permanent Environment Effect for protein, (kg). + E_temporary_fat : float + Temporary Environment Effect for fat, (kg). + E_temporary_protein : float + Temporary Environment Effect for protein, (kg). + phenotype_fat : float + Phenotype for fat, (kg). + phenotype_protein : float + Phenotype for protein, (kg). + EBV_fat : float + Estimated Breeding Value for fat, (kg). + EBV_protein : float + Estimated Breeding Value for protein, (kg). + ranking_index : float + Ranking index for animal performance. """ - net_merit: dict[str, dict[str, dict[str, float]]] = {} - top_semen: dict[str, dict[str, float]] = {} - year_month_of_first_net_merit_value: str - year_month_of_last_net_merit_value: str - year_month_of_first_top_semen_value: str - year_month_of_last_top_semen_value: str - - @classmethod - def initialize_class_variables(cls) -> None: - """This method initializes the class variables for net_merit and top_semen.""" - im = InputManager() - net_merit_HO: dict[str, list[str | float]] = im.get_data("animal_net_merit") - top_listing_semen_HO: dict[str, list[str | float]] = im.get_data("animal_top_listing_semen") - - cls.net_merit = { - SUPPORTED_BREED: { - net_merit_HO["year_month"][i]: {"average": net_merit_HO["average"][i], "std": net_merit_HO["std"][i]} - for i in range(len(net_merit_HO["year_month"])) - } - } - cls.net_merit = cls.net_merit_base_change(cls.net_merit) - cls.net_merit = cls.net_merit_fill_gap(cls.net_merit) - - cls.year_month_of_first_net_merit_value = min(cls.net_merit[SUPPORTED_BREED].keys()) - cls.year_month_of_last_net_merit_value = max(cls.net_merit[SUPPORTED_BREED].keys()) - - cls.top_semen = { - SUPPORTED_BREED: { - top_listing_semen_HO["year_month"][i]: top_listing_semen_HO["estimated_PTA"][i] - for i in range(len(top_listing_semen_HO["year_month"])) - } - } - - cls.year_month_of_first_top_semen_value = min(cls.top_semen[SUPPORTED_BREED].keys()) - cls.year_month_of_last_top_semen_value = max(cls.top_semen[SUPPORTED_BREED].keys()) - - @staticmethod - def net_merit_base_change( - original_net_merit: dict[str, dict[str, dict[str, float]]], - ) -> dict[str, dict[str, dict[str, float]]]: - """ - This function performs the base change for the net merit data. + TBV_fat: float + TBV_protein: float + E_permanent_fat: float + E_permanent_protein: float + + E_temporary_fat: float + E_temporary_protein: float + phenotype_fat: float + phenotype_protein: float + EBV_fat: float + EBV_protein: float + ranking_index: float + + def __init__( + self, + birth_year: int, + animal_type: AnimalType, + *, + parity: int | None = None, + initialize_new_born_calf: bool = False, + dam_tbv_fat: float | None = None, + dam_tbv_protein: float | None = None, + birth_month: int | None = None, + ) -> None: + """Initialize genetic attributes.""" + if initialize_new_born_calf: + assert ( + animal_type == AnimalType.CALF + and dam_tbv_fat is not None + and dam_tbv_protein is not None + and birth_month is not None + ) + self.TBV_fat, self.TBV_protein = self._calculate_newborn_calf_tbv_values( + dam_tbv_fat, dam_tbv_protein, f"{birth_year}-{birth_month:02d}" + ) + else: + self.TBV_fat, self.TBV_protein = self._calculate_tbv_values() + self.E_permanent_fat, self.E_permanent_protein = self._calculate_ep_values() + self.E_temporary_fat, self.E_temporary_protein = self._calculate_et_values() + self.phenotype_fat, self.phenotype_protein = self._calculate_phenotype_values(birth_year=birth_year) + self.EBV_fat, self.EBV_protein = 0.0, 0.0 + self.ranking_index = 0.0 + + def calculate_ebv_and_ranking_index( + self, + animal_type: AnimalType, + group_specific_TBV_fat_mean: float, + group_specific_TBV_protein_mean: float, + parity: int | None, + ) -> None: + """Calculate EBV and ranking index values.""" + self.EBV_fat, self.EBV_protein = self._calculate_ebv_values( + animal_type=animal_type, + group_specific_TBV_fat_mean=group_specific_TBV_fat_mean, + group_specific_TBV_protein_mean=group_specific_TBV_protein_mean, + parity=parity, + ) + self.ranking_index = self._calculate_ranking_index() + + def recalculate_values_at_lactation_start( + self, + birth_year: int, + animal_type: AnimalType, + parity: int, + group_specific_TBV_fat_mean: float, + group_specific_TBV_protein_mean: float, + ) -> None: + """Recalculate genetic values at lactation start.""" + self.E_temporary_fat, self.E_temporary_protein = self._calculate_et_values() + self.phenotype_fat, self.phenotype_protein = self._calculate_phenotype_values(birth_year=birth_year) + if parity <= 3: + self.EBV_fat, self.EBV_protein = self._calculate_ebv_values( + animal_type=animal_type, + parity=parity, + group_specific_TBV_fat_mean=group_specific_TBV_fat_mean, + group_specific_TBV_protein_mean=group_specific_TBV_protein_mean, + ) + self.ranking_index = self._calculate_ranking_index() - Parameters - ---------- - original_net_merit: dict[str, dict[str, dict[str, float]]] - The original net merit data, $USD. + def _calculate_tbv_values(self) -> tuple[float, float]: + """Calculate TBV values for an animal entering the herd.""" + tbv_fat, tbv_protein = Utility.generate_bivariate_random_numbers( + 0.0, 0.0, TBV_FAT_STD, TBV_PROTEIN_STD, TBV_CORRELATION + ) + return tbv_fat, tbv_protein - Returns - ------- - dict[str, dict[str, dict[str, float]]] - The net merit data after base change, $USD. + def _calculate_newborn_calf_tbv_values( + self, dam_tbv_fat: float, dam_tbv_protein: float, birth_year_month: str + ) -> tuple[float, float]: + """Calculate TBV values for a newborn calf.""" + tbv_fat_top_semen = AnimalConfig.top_listing_semen["estimated_fat"][birth_year_month] + tbv_protein_top_semen = AnimalConfig.top_listing_semen["estimated_protein"][birth_year_month] + std_tbv_fat_national_average, std_tbv_protein_national_average = TBV_FAT_STD, TBV_PROTEIN_STD - Notes - ----- - The CDCB reevaluates and adjusts the net merit value to keep it within a reasonable range by changing the - baseline every five years. Therefore, to realign the past data with the current values, we first change every - value to match the oldest value, and shift all data back into the reasonable range. + mean_tbv_fat = (tbv_fat_top_semen + dam_tbv_fat) / 2 + mean_tbv_protein = (tbv_protein_top_semen + dam_tbv_protein) / 2 - References - ---------- - https://aipl.arsusda.gov/reference/base2010.htm + std_tbv_fat = np.sqrt(std_tbv_fat_national_average**2 / 2) + std_tbv_protein = np.sqrt(std_tbv_protein_national_average**2 / 2) - https://aipl.arsusda.gov/reference/base2014.htm + tbv_fat, tbv_protein = Utility.generate_bivariate_random_numbers( + mean_tbv_fat, mean_tbv_protein, std_tbv_fat, std_tbv_protein, TBV_CORRELATION + ) - https://uscdcb.com/wp-content/uploads/2020/02/Norman-et-al-Genetic-Base-Change-April-2020-FINAL_new.pdf - """ - adjusted_net_merit: dict[str, dict[str, dict[str, float]]] = {} - total_adjustment_value = sum([BASE_CHANGE_LOOKUP_TABLE[i] for i in BASE_CHANGE_LOOKUP_TABLE.keys()]) - for breed in original_net_merit.keys(): - adjusted_net_merit[breed] = {} - for year_month in original_net_merit[breed].keys(): - adjusted_net_merit[breed][year_month] = {} - datetime_year_month = date.fromisoformat(year_month + "-01") - increase = sum( - [ - BASE_CHANGE_LOOKUP_TABLE[base_change_time] - for base_change_time in BASE_CHANGE_LOOKUP_TABLE.keys() - if datetime_year_month >= base_change_time - ] - ) - original_value = original_net_merit[breed][year_month]["average"] + increase - adjusted_value = original_value - total_adjustment_value - adjusted_net_merit[breed][year_month]["average"] = adjusted_value - adjusted_net_merit[breed][year_month]["std"] = original_net_merit[breed][year_month]["std"] - return adjusted_net_merit + return tbv_fat, tbv_protein - @staticmethod - def net_merit_fill_gap( - original_net_merit: dict[str, dict[str, dict[str, float]]], - ) -> dict[str, dict[str, dict[str, float]]]: - """ - The input net merit data only has three entries per year, this function fills in the gap in between entries by - using linear approximation. + def _calculate_ep_values(self) -> tuple[float, float]: + """Calculate Permanent Environment Effect (E_permanent) values.""" + ep_fat, ep_protein = Utility.generate_bivariate_random_numbers( + 0.0, 0.0, E_PERMANENT_FAT_STD, E_PERMANENT_PROTEIN_STD, E_PERMANENT_CORRELATION + ) + return ep_fat, ep_protein - Parameters - ---------- - original_net_merit: dict[str, dict[str, dict[str, float]]] - The original net merit data, $USD. + def _calculate_et_values(self) -> tuple[float, float]: + """Calculate Temporary Environment Effect (E_temporary) values.""" + et_fat, et_protein = Utility.generate_bivariate_random_numbers( + 0.0, 0.0, E_TEMPORARY_FAT_STD, E_TEMPORARY_PROTEIN_STD, E_TEMPORARY_CORRELATION + ) + return et_fat, et_protein + + def _calculate_phenotype_values(self, birth_year: int) -> tuple[float, float]: + """Calculate phenotype values.""" + mean_fat = AnimalConfig.average_phenotype["fat_kg"][birth_year] + mean_protein = AnimalConfig.average_phenotype["protein_kg"][birth_year] + phenotype_fat = mean_fat + self.TBV_fat + self.E_permanent_fat + self.E_temporary_fat + phenotype_protein = mean_protein + self.TBV_protein + self.E_permanent_protein + self.E_temporary_protein + return phenotype_fat, phenotype_protein + + def _calculate_ebv_values( + self, + animal_type: AnimalType, + group_specific_TBV_fat_mean: float, + group_specific_TBV_protein_mean: float, + parity: int | None, + ) -> tuple[float, float]: + """Calculate EBV values.""" + parity_index = min(parity, 3) if animal_type.is_cow and parity is not None else 0 + fat_accuracy, protein_accuracy = FAT_ACCURACY_BY_PARITY[parity_index], PROTEIN_ACCURACY_BY_PARITY[parity_index] + + mean_ebv_fat = group_specific_TBV_fat_mean + (self.TBV_fat - group_specific_TBV_fat_mean) * (fat_accuracy**2) + mean_ebv_protein = group_specific_TBV_protein_mean + (self.TBV_protein - group_specific_TBV_protein_mean) * ( + protein_accuracy**2 + ) - Returns - ------- - dict[str, dict[str, dict[str, float]]] - The net merit data after filling the gap in between entries, $USD. - """ - expanded_net_merit: dict[str, dict[str, dict[str, float]]] = {} - monthly_increase_lookup = {2005: 132 / 60, 2010: 184 / 60, 2015: 231 / 60, 2020: (360.731239 - 36.931108) / 48} + std_ebv_fat = np.sqrt((1 - fat_accuracy**2) * (fat_accuracy**2) * TBV_FAT_STD) + std_ebv_protein = np.sqrt((1 - protein_accuracy**2) * (protein_accuracy**2) * TBV_PROTEIN_STD) - for breed in original_net_merit.keys(): - expanded_net_merit[breed] = {} - years = [int(year_month[:4]) for year_month in original_net_merit[breed].keys()] - max_year = max(years) - current_keys = list(original_net_merit[breed].keys()) - for year_month in current_keys: - expanded_net_merit[breed][year_month] = { - "average": original_net_merit[breed][year_month]["average"], - "std": original_net_merit[breed][year_month]["std"], - } - year, month = int(year_month[:4]), int(year_month[5:]) + noise_ebv_fat = np.random.normal(0.0, std_ebv_fat) + noise_ebv_protein = np.random.normal(0.0, std_ebv_protein) - if month < 12: - month += 1 - else: - year += 1 - month = 1 - next_year_month = str(year) + "-" + str(month).zfill(2) - num_inc = 1 - while next_year_month not in current_keys: - average_monthly_increase_key = year - (year % 5) - average_monthly_increase = monthly_increase_lookup[average_monthly_increase_key] - expanded_net_merit[breed][next_year_month] = { - "average": original_net_merit[breed][year_month]["average"] - + num_inc * average_monthly_increase, - "std": original_net_merit[breed][year_month]["std"], - } - if month < 12: - month += 1 - else: - year += 1 - month = 1 - next_year_month = str(year) + "-" + str(month).zfill(2) - num_inc += 1 - if year > max_year: - break + ebv_fat = mean_ebv_fat + noise_ebv_fat + ebv_protein = mean_ebv_protein + noise_ebv_protein + return ebv_fat, ebv_protein - updated_keys = list(expanded_net_merit[breed].keys()) - updated_keys.sort() - expanded_net_merit[breed] = {k: expanded_net_merit[breed][k] for k in updated_keys} - return expanded_net_merit + def _calculate_ranking_index(self) -> float: + """Calculate ranking index.""" + return 0.318 * self.EBV_fat + 0.13 * self.EBV_protein @staticmethod - def assign_net_merit_value_to_animals_entering_herd(birth_date: str, breed: Breed) -> float: - """ - This function calculates the net merit value for animals entering the herd, either during initialization or - for animals bought during the simulation. - - Parameters - ---------- - birth_date: str - The birthdate of the animal in the format "YYYY-MM-DD". - breed: str - The breed of the animal. - - Returns - ------- - float - The net merit value of the animal, $USD. - - Notes - ----- - With the birthdate and the breed of the animal, this function first looks up the mean and standard deviation - of the net merit value, then generates a random value from the distribution as the net merit value. - """ - birth_year_month = birth_date[:7] - birth_year_month = AnimalGenetics._clamp_birth_year_month_in_data_range(birth_year_month, is_for_net_merit=True) - average = AnimalGenetics.net_merit[breed.name][birth_year_month]["average"] - std = AnimalGenetics.net_merit[breed.name][birth_year_month]["std"] - return Utility.generate_random_number(average, std) + def calculate_average_genetic_values(list_of_genetics: list["Genetics"]) -> dict[str, float | None]: + """Calculate average genetic values for a list of genetics.""" + if (num_animal := len(list_of_genetics)) <= 0: + return { + "TBV_fat": None, + "TBV_protein": None, + "E_permanent_fat": None, + "E_permanent_protein": None, + "E_temporary_fat": None, + "E_temporary_protein": None, + "phenotype_fat": None, + "phenotype_protein": None, + "EBV_fat": None, + "EBV_protein": None, + "ranking_index": None, + } + else: + return { + "TBV_fat": sum([genetic.TBV_fat for genetic in list_of_genetics]) / num_animal, + "TBV_protein": sum([genetic.TBV_protein for genetic in list_of_genetics]) / num_animal, + "E_permanent_fat": sum([genetic.E_permanent_fat for genetic in list_of_genetics]) / num_animal, + "E_permanent_protein": sum([genetic.E_permanent_protein for genetic in list_of_genetics]) / num_animal, + "E_temporary_fat": sum([genetic.E_temporary_fat for genetic in list_of_genetics]) / num_animal, + "E_temporary_protein": sum([genetic.E_temporary_protein for genetic in list_of_genetics]) / num_animal, + "phenotype_fat": sum([genetic.phenotype_fat for genetic in list_of_genetics]) / num_animal, + "phenotype_protein": sum([genetic.phenotype_protein for genetic in list_of_genetics]) / num_animal, + "EBV_fat": sum([genetic.EBV_fat for genetic in list_of_genetics]) / num_animal, + "EBV_protein": sum([genetic.EBV_protein for genetic in list_of_genetics]) / num_animal, + "ranking_index": sum([genetic.ranking_index for genetic in list_of_genetics]) / num_animal, + } @staticmethod - def assign_net_merit_value_to_newborn_calf(time: RufasTime, breed: Breed, dam_net_merit_value: float) -> float: - """ - This function calculates the net merit value for the newborn calves. - - Parameters - ---------- - time: RufasTime - The RufasTime instance that contains the birthdate of the newborn calf. - This function will be called on the day of birth for the newborn calf; therefore, the current date will be - the birthdate of the calf. - breed: str - The breed of the newborn calf. - dam_net_merit_value: float - The net merit value of the dam (mother cow). - - Returns - ------- - float - The net merit value of the newborn calf, $USD. - - Notes - ----- - With the birthdate and the breed of the animal, this function first looks up the top listing semen value. - The mean for the net merit value of the newborn can then be calculated as the sum of the top listing semen - value and the net merit value of the dam. - The standard deviation is the square root of the Mendelian sampling variance, which is simply half of the - population variance. - """ - birth_year_month = str(time.current_calendar_year) + "-" + str(time.current_month).zfill(2) - net_merit_birth_year_month = AnimalGenetics._clamp_birth_year_month_in_data_range( - birth_year_month, is_for_net_merit=True - ) - top_semen_birth_year_month = AnimalGenetics._clamp_birth_year_month_in_data_range( - birth_year_month, is_for_net_merit=False + def calculate_average_tbv(list_of_genetics: list["Genetics"]) -> tuple[float, float]: + """Calculate average TBV values for a specific group of animals.""" + num_animals = len(list_of_genetics) + return ( + sum([genetic.TBV_fat for genetic in list_of_genetics]) / num_animals if num_animals > 0 else 0.0, + sum([genetic.TBV_protein for genetic in list_of_genetics]) / num_animals if num_animals > 0 else 0.0, ) - semen_predicted_transmitting_ability: float = AnimalGenetics.top_semen[breed.name][top_semen_birth_year_month] - average_net_merit = semen_predicted_transmitting_ability + dam_net_merit_value - variance = ((AnimalGenetics.net_merit[breed.name][net_merit_birth_year_month]["std"]) ** 2) / 2 - return Utility.generate_random_number(average_net_merit, sqrt(variance)) - - @staticmethod - def _clamp_birth_year_month_in_data_range(birth_year_month: str, is_for_net_merit: bool) -> str: - """ - Checks if the birth month of an animal is available in either the net merit or top semen data, and clamps the - month to the earliest or latest date if the date is not available. - - Parameters - ---------- - birth_year_month : str - The year and month of an animal's birth, in the format "YYYY-MM". - is_for_net_merit : bool - True if the birth month is being checked against the net merit data, False if it is being checked against - the top semen data. - - Returns - ------- - str - The birth month of the animal, clamped between the earliest and latest dates available. - - """ - if is_for_net_merit: - earliest_date = AnimalGenetics.year_month_of_first_net_merit_value - latest_date = AnimalGenetics.year_month_of_last_net_merit_value - data_type = "net merit" - else: - earliest_date = AnimalGenetics.year_month_of_first_top_semen_value - latest_date = AnimalGenetics.year_month_of_last_top_semen_value - data_type = "top semen" - is_birth_date_in_range = earliest_date <= birth_year_month <= latest_date - if not is_birth_date_in_range: - clamped_birth_year_month = min(max(earliest_date, birth_year_month), latest_date) - om = OutputManager() - info_map = { - "class": AnimalGenetics.__class__.__name__, - "function": AnimalGenetics._clamp_birth_year_month_in_data_range.__name__, - "birth_year_month": birth_year_month, - "date_of_earliest_data": earliest_date, - "date_of_latest_data": latest_date, - "type_of_genetic_data": data_type, - } - om.add_error( - "Animal birthdate out of range for animal genetics data", - f"No {data_type} data for {birth_year_month}, using data from closest available date: " - f"{clamped_birth_year_month}", - info_map, - ) - birth_year_month = clamped_birth_year_month - return birth_year_month diff --git a/RUFAS/biophysical/animal/animal_module_reporter.py b/RUFAS/biophysical/animal/animal_module_reporter.py index 29dc56519d..dccf740216 100644 --- a/RUFAS/biophysical/animal/animal_module_reporter.py +++ b/RUFAS/biophysical/animal/animal_module_reporter.py @@ -2,6 +2,8 @@ from dataclasses import asdict from typing import Any +from RUFAS.biophysical.animal.animal_genetics.animal_genetics import UNITS as genetics_units +from RUFAS.biophysical.animal.data_types.animal_events import AnimalEvents from RUFAS.biophysical.animal.data_types.animal_population import AnimalPopulationStatistics from RUFAS.biophysical.animal.data_types.animal_typed_dicts import SoldAnimalTypedDict, StillbornCalfTypedDict from RUFAS.biophysical.animal.data_types.herd_statistics import HerdStatistics @@ -163,7 +165,7 @@ def report_milk(cls, milk_reports: list[MilkProductionStatistics], simulation_da } for milk_stats in milk_reports: - updated_milk_data: dict[str, int | float] = asdict(milk_stats) + updated_milk_data: dict[str, int | float | str] = asdict(milk_stats) updated_milk_data["is_milking"] = milk_stats.is_milking updated_milk_data["estimated_daily_milk_produced"] = milk_stats.estimated_daily_milk_produced updated_milk_data["milk_protein"] = milk_stats.milk_protein @@ -173,8 +175,42 @@ def report_milk(cls, milk_reports: list[MilkProductionStatistics], simulation_da updated_milk_data["cow_id"] = milk_stats.cow_id updated_milk_data["pen_id"] = milk_stats.pen_id updated_milk_data["simulation_day"] = simulation_day + updated_milk_data["animal_type"] = milk_stats.animal_type.name om.add_variable("milk_data_at_milk_update", updated_milk_data, info_map) + @classmethod + def report_average_genetics( + cls, average_genetics: dict[str, float | None], variable_name_prefix: str, simulation_day: int + ) -> None: + """ + Reports the average genetics data with associated simulation metadata. The + method adds the given genetics data to a managed output variable, using + a specific variable name prefix and simulation day. + + Parameters + ---------- + average_genetics : dict[str, float | None] + A dictionary containing genetic measurements, where the key is + the measurement name and the value is the averaged measurement. + variable_name_prefix : str + A prefix to be appended to the variable name for distinguishing + the reported data in the output. + simulation_day : int + The specific day in the simulation timeline, used to annotate the + reported data for temporal tracking. + + Returns + ------- + None + """ + info_map = { + "class": AnimalModuleReporter.__name__, + "function": AnimalModuleReporter.report_average_genetics.__name__, + "units": genetics_units, + "simulation_day": simulation_day, + } + om.add_variable(f"{variable_name_prefix}_average_genetics", average_genetics, info_map) + @classmethod def report_ration_per_animal( cls, @@ -928,6 +964,9 @@ def report_sold_animal_information(cls, herd_statistics: HerdStatistics) -> None ) om.add_variable("days_in_milk", animal["days_in_milk"], dict(info_map, **{"units": MeasurementUnits.DAYS})) om.add_variable("parity", animal["parity"], dict(info_map, **{"units": MeasurementUnits.UNITLESS})) + om.add_variable( + "genetic_history", animal["genetic_history"], dict(info_map, **{"units": MeasurementUnits.UNITLESS}) + ) @classmethod def report_stillborn_calves_information( @@ -1091,8 +1130,9 @@ def report_end_of_simulation( herd_statistics: HerdStatistics, herd_reproduction_statistics: HerdReproductionStatistics, time: RufasTime, - heiferII_events_by_id: dict[str, str], - cow_events_by_id: dict[str, str], + heiferII_events_by_id: dict[str, AnimalEvents], + cow_events_by_id: dict[str, AnimalEvents], + all_animals_genetic_history: dict[int, str], ) -> None: """ Calls all reporter methods that should happen at the end of the simulation. @@ -1105,10 +1145,12 @@ def report_end_of_simulation( Instance of HerdReproductionStatistics class. time : RufasTime The RufasTime object with the current time information. - heiferII_events_by_id : dict[str, str] + heiferII_events_by_id : dict[str, AnimalEvents] The dictionary of HeiferII events. - cow_events_by_id : dict[str, str] + cow_events_by_id : dict[str, AnimalEvents] The dictionary of Cow events. + all_animals_genetic_history : dict[int, str] + The dict of genetic histories for all animals in the herd by their IDs. """ empty_sold_animals: list[SoldAnimalTypedDict] = [{"sold_at_day": 0, "body_weight": 0}] AnimalModuleReporter.report_sold_animal_information(herd_statistics) @@ -1183,15 +1225,16 @@ def report_end_of_simulation( AnimalModuleReporter._record_heiferIIs_conception_rate(herd_reproduction_statistics) AnimalModuleReporter._record_cows_conception_rate(herd_reproduction_statistics) + AnimalModuleReporter._report_all_animals_genetic_history(all_animals_genetic_history) @classmethod - def _record_animal_events(cls, animal_events_by_id: dict[str, str], simulation_day: int) -> None: + def _record_animal_events(cls, animal_events_by_id: dict[str, AnimalEvents], simulation_day: int) -> None: """ Record the events of the animals. Parameters ---------- - animal_events_by_id : dict[str, str] + animal_events_by_id : dict[str, AnimalEvents] A dictionary of animal events, where the key is a string containing the animal id and the animal type, and the value is the string representation of the events of the animal. simulation_day : int @@ -1307,6 +1350,16 @@ def _record_cows_conception_rate(cls, herd_reproduction_statistics: HerdReproduc dict(info_map, **{"units": MeasurementUnits.CONCEPTIONS_PER_SERVICE}), ) + @classmethod + def _report_all_animals_genetic_history(cls, all_animals_genetic_history: dict[int, str]) -> None: + """Report all animals genetic history.""" + info_map = { + "class": AnimalModuleReporter.__name__, + "function": AnimalModuleReporter._report_all_animals_genetic_history.__name__, + "units": MeasurementUnits.UNITLESS, + } + om.add_variable("animals_genetic_history", all_animals_genetic_history, info_map) + @classmethod def report_animal_population_statistics(cls, prefix: str, herd_summary: AnimalPopulationStatistics) -> None: """Reports the herd summary statistics for the starting animal population.""" diff --git a/RUFAS/biophysical/animal/data_types/animal_typed_dicts.py b/RUFAS/biophysical/animal/data_types/animal_typed_dicts.py index 61c9f9767e..7ed608bd5c 100644 --- a/RUFAS/biophysical/animal/data_types/animal_typed_dicts.py +++ b/RUFAS/biophysical/animal/data_types/animal_typed_dicts.py @@ -18,7 +18,6 @@ class CalfValuesTypedDict(TypedDict): wean_weight: float mature_body_weight: float events: str - net_merit: float initial_phosphorus: NotRequired[float] body_weight_history: NotRequired[list[BodyWeightHistory]] pen_history: NotRequired[list[PenHistory]] @@ -36,7 +35,8 @@ class NewBornCalfValuesTypedDict(TypedDict): days_born: int birth_weight: float initial_phosphorus: float - net_merit: float + dam_tbv_fat: NotRequired[float] + dam_tbv_protein: NotRequired[float] body_weight_history: NotRequired[list[BodyWeightHistory]] pen_history: NotRequired[list[PenHistory]] conceptus_weight: NotRequired[float] @@ -55,7 +55,6 @@ class HeiferIValuesTypedDict(TypedDict): wean_weight: float mature_body_weight: float events: str - net_merit: float body_weight_history: NotRequired[list[BodyWeightHistory]] pen_history: NotRequired[list[PenHistory]] conceptus_weight: NotRequired[float] @@ -74,7 +73,6 @@ class HeiferIIValuesTypedDict(TypedDict): wean_weight: float mature_body_weight: float events: str - net_merit: float body_weight_history: NotRequired[list[BodyWeightHistory]] pen_history: NotRequired[list[PenHistory]] conceptus_weight: NotRequired[float] @@ -109,7 +107,6 @@ class HeiferIIIValuesTypedDict(TypedDict): wean_weight: float mature_body_weight: float events: str - net_merit: float body_weight_history: NotRequired[list[BodyWeightHistory]] pen_history: NotRequired[list[PenHistory]] conceptus_weight: NotRequired[float] @@ -144,7 +141,6 @@ class CowValuesTypedDict(TypedDict): wean_weight: float mature_body_weight: float events: str - net_merit: float body_weight_history: NotRequired[list[BodyWeightHistory]] pen_history: NotRequired[list[PenHistory]] conceptus_weight: NotRequired[float] @@ -186,6 +182,7 @@ class SoldAnimalTypedDict(TypedDict): cull_reason: str | None days_in_milk: int | str parity: int | str + genetic_history: str class StillbornCalfTypedDict(TypedDict): diff --git a/RUFAS/biophysical/animal/data_types/genetic_history.py b/RUFAS/biophysical/animal/data_types/genetic_history.py new file mode 100644 index 0000000000..240324233f --- /dev/null +++ b/RUFAS/biophysical/animal/data_types/genetic_history.py @@ -0,0 +1,73 @@ +from typing import TypedDict + +from RUFAS.biophysical.animal.data_types.animal_types import AnimalType + + +class GeneticHistory(TypedDict): + """ + A class to represent the genetic history of an individual animal on a farm. + + This class is used to track the genetic attributes of an animal over the course of a simulation. + It contains information about the simulation day, the age of the animal in days, and its genetic attributes. + + Attributes + ---------- + simulation_day : int + The day of the simulation corresponding to the genetic record of the animal. + id : int + The unique identifier of the animal. + days_born : int + The number of days since the birth of the animal. + animal_type : AnimalType + The type of animal, (unitless). + days_in_milk : int + The number of days the animal has been in milk production, (simulation days). + days_in_pregnancy : int + The number of days the animal has been pregnant, (simulation days). + parity : int + The number of times the animal has given birth. + TBV_fat : float + The True Breed Value for fat of the animal, (kg). + TBV_protein : float + The True Breed Value for protein of the animal, (kg). + E_permanent_fat : float + The Permanent Environment Effect for fat of the animal, (kg). + E_permanent_protein : float + The Permanent Environment Effect for protein of the animal, (kg). + E_temporary_fat : float + The Temporary Environment Effect for fat of the animal, (kg). + E_temporary_protein : float + The Temporary Environment Effect for protein of the animal, (kg). + phenotype_fat : float + The fat phenotype of the animal, (kg). + phenotype_protein : float + The protein phenotype of the animal, (kg). + EBV_fat : float + The Estimated Breeding Value for fat of the animal, (kg). + EBV_protein : float + The Estimated Breeding Value for protein of the animal, (kg). + ranking_index : float + The ranking index of the animal, (unitless). + """ + + simulation_day: int + + id: int + days_born: int + animal_type: AnimalType + days_in_milk: int + days_in_pregnancy: int + parity: int + + TBV_fat: float + TBV_protein: float + E_permanent_fat: float + E_permanent_protein: float + + E_temporary_fat: float + E_temporary_protein: float + phenotype_fat: float + phenotype_protein: float + EBV_fat: float + EBV_protein: float + ranking_index: float diff --git a/RUFAS/biophysical/animal/data_types/milk_production.py b/RUFAS/biophysical/animal/data_types/milk_production.py index da45e9f3c2..21602a4378 100644 --- a/RUFAS/biophysical/animal/data_types/milk_production.py +++ b/RUFAS/biophysical/animal/data_types/milk_production.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from RUFAS.biophysical.animal.data_types.animal_events import AnimalEvents +from RUFAS.biophysical.animal.data_types.animal_types import AnimalType from RUFAS.units import MeasurementUnits @@ -76,6 +77,34 @@ class MilkProductionStatistics: The daily lactose content in the milk produced by the animal, (kg/day). parity : int The number of claves the cow has given birth, (unitless). + days_born : int + The number of days since the birth of the animal, (days). + days_in_pregnancy : int + The number of days since the animal has been pregnant, (days). + animal_type : AnimalType + The type of animal, (unitless). + TBV_fat : float + The True Breed Value for fat of the animal, (kg). + TBV_protein : float + The True Breed Value for protein of the animal, (kg). + E_permanent_fat : float + The Permanent Environment Effect for fat of the animal, (kg). + E_permanent_protein : float + The Permanent Environment Effect for protein of the animal, (kg). + E_temporary_fat : float + The Temporary Environment Effect for fat of the animal, (kg). + E_temporary_protein : float + The Temporary Environment Effect for protein of the animal, (kg). + phenotype_fat : float + The fat phenotype of the animal, (kg). + phenotype_protein : float + The protein phenotype of the animal, (kg). + EBV_fat : float + The Estimated Breeding Value for fat of the animal, (kg). + EBV_protein : float + The Estimated Breeding Value for protein of the animal, (kg). + ranking_index : float + The ranking index of the animal, (unitless). """ cow_id: int @@ -86,6 +115,22 @@ class MilkProductionStatistics: milk_fat: float milk_lactose: float parity: int + days_born: int + days_in_pregnancy: int + animal_type: AnimalType + + TBV_fat: float + TBV_protein: float + E_permanent_fat: float + E_permanent_protein: float + + E_temporary_fat: float + E_temporary_protein: float + phenotype_fat: float + phenotype_protein: float + EBV_fat: float + EBV_protein: float + ranking_index: float UNITS = { "cow_id": MeasurementUnits.UNITLESS, @@ -98,6 +143,20 @@ class MilkProductionStatistics: "parity": MeasurementUnits.UNITLESS, "is_milking": MeasurementUnits.UNITLESS, "simulation_day": MeasurementUnits.SIMULATION_DAY, + "days_born": MeasurementUnits.DAYS, + "days_in_pregnancy": MeasurementUnits.DAYS, + "animal_type": MeasurementUnits.UNITLESS, + "TBV_fat": MeasurementUnits.KILOGRAMS, + "TBV_protein": MeasurementUnits.KILOGRAMS, + "E_permanent_fat": MeasurementUnits.KILOGRAMS, + "E_permanent_protein": MeasurementUnits.KILOGRAMS, + "E_temporary_fat": MeasurementUnits.KILOGRAMS, + "E_temporary_protein": MeasurementUnits.KILOGRAMS, + "phenotype_fat": MeasurementUnits.KILOGRAMS, + "phenotype_protein": MeasurementUnits.KILOGRAMS, + "EBV_fat": MeasurementUnits.KILOGRAMS, + "EBV_protein": MeasurementUnits.KILOGRAMS, + "ranking_index": MeasurementUnits.UNITLESS, } @property diff --git a/RUFAS/biophysical/animal/data_types/reproduction.py b/RUFAS/biophysical/animal/data_types/reproduction.py index ba9384db53..78c77f169d 100644 --- a/RUFAS/biophysical/animal/data_types/reproduction.py +++ b/RUFAS/biophysical/animal/data_types/reproduction.py @@ -25,8 +25,10 @@ class ReproductionInputs: The number of days the animal has been in pregnancy, (simulation days). days_in_milk : int The number of days the animal has been lactating, (simulation days). - net_merit : float - The genetic merit score of the animal, (lifetime USD). + dam_tbv_fat : float + The True Breeding Value for fat of the animal, (kg). + dam_tbv_protein : float + The True Breeding Value for protein of the animal, (kg). phosphorus_for_gestation_required_for_calf : float The amount of phosphorus required for fetal development during the gestation period in the animal. @@ -38,7 +40,8 @@ class ReproductionInputs: days_born: int days_in_pregnancy: int days_in_milk: int - net_merit: float + dam_tbv_fat: float + dam_tbv_protein: float phosphorus_for_gestation_required_for_calf: float @property @@ -291,8 +294,10 @@ class ReproductionDataStream: The current number of days the animal has been milking, (simulation days). events : AnimalEvents Associated events relevant to the animal’s lifecycle and reproduction. - net_merit : float - Net merit value, representing the economic value of the animal, (lifetime USD). + dam_tbv_fat : float + The True Breeding Value for fat of the animal, (kg). + dam_tbv_protein : float + The True Breeding Value for protein of the animal, (kg). phosphorus_for_gestation_required_for_calf : float The phosphorus needed for gestation, specifically for the growth of the calf, (kg). individual animal. @@ -310,7 +315,8 @@ class ReproductionDataStream: days_in_pregnancy: int days_in_milk: int events: AnimalEvents - net_merit: float + dam_tbv_fat: float + dam_tbv_protein: float phosphorus_for_gestation_required_for_calf: float herd_reproduction_statistics: HerdReproductionStatistics diff --git a/RUFAS/biophysical/animal/herd_factory.py b/RUFAS/biophysical/animal/herd_factory.py index 0555e7526d..25d82f26a7 100644 --- a/RUFAS/biophysical/animal/herd_factory.py +++ b/RUFAS/biophysical/animal/herd_factory.py @@ -9,7 +9,7 @@ from RUFAS.biophysical.animal import animal_constants from RUFAS.biophysical.animal.animal import Animal from RUFAS.biophysical.animal.animal_config import AnimalConfig -from RUFAS.biophysical.animal.animal_genetics.animal_genetics import AnimalGenetics +from RUFAS.biophysical.animal.animal_genetics.animal_genetics import Genetics from RUFAS.biophysical.animal.animal_module_constants import AnimalModuleConstants from RUFAS.biophysical.animal.animal_module_reporter import AnimalModuleReporter from RUFAS.biophysical.animal.data_types.animal_enums import AnimalStatus, Breed @@ -86,7 +86,6 @@ def __init__( self.CI = self.im.get_data("animal.animal_config.farm_level.repro.calving_interval") self.initial_animal_num = self.im.get_data("animal.herd_initialization.initial_animal_num") self.simulation_days = self.im.get_data("animal.herd_initialization.simulation_days") - AnimalGenetics.initialize_class_variables() self.pre_animal_population = AnimalPopulation( calves=[], @@ -317,7 +316,6 @@ def _cow_give_birth(self, cow: Animal) -> None: days_born=0, initial_phosphorus=cow.nutrients.phosphorus_for_gestation_required_for_calf, birth_weight=cow.reproduction.calf_birth_weight, - net_merit=0.0, animal_type=AnimalType.CALF.value, ) cow.nutrients.total_phosphorus_in_animal = ( @@ -329,10 +327,9 @@ def _cow_give_birth(self, cow: Animal) -> None: cow.nutrients.phosphorus_for_gestation_required_for_calf = 0.0 cow.reproduction.calf_birth_weight = 0.0 - calf = Animal(args) + calf = Animal(args, self.time) if not calf.sold: self.pre_animal_population.calves.append(calf) - calf.net_merit = AnimalGenetics.assign_net_merit_value_to_newborn_calf(self.time, calf.breed, cow.net_merit) def _heiferIIIs_update(self, day: int) -> None: """ @@ -386,20 +383,15 @@ def _generate_animals(self) -> AnimalPopulation: args = NewBornCalfValuesTypedDict( id=self.pre_animal_population.next_id(), breed=self.breed.name, - birth_date="", + birth_date=self.time.current_date.strftime("%Y-%m-%d"), days_born=0, initial_phosphorus=0, birth_weight=birth_weight, - net_merit=0.0, animal_type=AnimalType.CALF.value, ) - calf = Animal(args) + calf = Animal(args, self.time) if not (calf.sold or calf.stillborn): self.pre_animal_population.calves.append(calf) - birth_date_str: str = self.time.current_date.strftime("%Y-%m-%d") - calf.net_merit = AnimalGenetics.assign_net_merit_value_to_animals_entering_herd( - birth_date_str, self.breed - ) for day in tqdm(range(self.simulation_days)): self._cows_update() @@ -422,11 +414,7 @@ def _init_animal_from_data(self, animal_type: str, animal_data: Any) -> Animal: animal_data.update(id=self.pre_animal_population.next_id()) if animal_type == "calf": animal_data.update(initial_phosphorus=0) - animal = Animal(animal_data) - animal_birth_date: str = self._backtrack_animal_birth_date(animal_data["days_born"], self.time) - animal.net_merit = AnimalGenetics.assign_net_merit_value_to_animals_entering_herd( - birth_date=animal_birth_date, breed=animal.breed - ) + animal = Animal(animal_data, self.time) return animal def _initialize_herd_from_data(self) -> AnimalPopulation: @@ -439,6 +427,7 @@ def _initialize_herd_from_data(self) -> AnimalPopulation: herd_data["calves"], ) ) + self._update_genetic_values(calves) heiferIs = list( map( self._init_animal_from_data, @@ -446,6 +435,7 @@ def _initialize_herd_from_data(self) -> AnimalPopulation: herd_data["heiferIs"], ) ) + self._update_genetic_values(heiferIs) heiferIIs = list( map( self._init_animal_from_data, @@ -453,6 +443,7 @@ def _initialize_herd_from_data(self) -> AnimalPopulation: herd_data["heiferIIs"], ) ) + self._update_genetic_values(heiferIIs) heiferIIIs = list( map( self._init_animal_from_data, @@ -460,6 +451,7 @@ def _initialize_herd_from_data(self) -> AnimalPopulation: herd_data["heiferIIIs"], ) ) + self._update_genetic_values(heiferIIIs) cows = list( map( self._init_animal_from_data, @@ -467,6 +459,7 @@ def _initialize_herd_from_data(self) -> AnimalPopulation: herd_data["cows"], ) ) + self._update_genetic_values(cows) replacement = list( map( self._init_animal_from_data, @@ -474,6 +467,7 @@ def _initialize_herd_from_data(self) -> AnimalPopulation: herd_data["replacement"], ) ) + self._update_genetic_values(replacement) return AnimalPopulation( calves=calves, @@ -485,6 +479,17 @@ def _initialize_herd_from_data(self) -> AnimalPopulation: current_animal_id=self.pre_animal_population.current_animal_id, ) + def _update_genetic_values( + self, + animals: list[Animal], + ) -> None: + """Function to update EBV and ranking index values of a specific group of animals.""" + mean_tbv_fat, mean_tbv_protein = Genetics.calculate_average_tbv([animal.genetics for animal in animals]) + for animal in animals: + animal.genetics.calculate_ebv_and_ranking_index( + animal.animal_type, mean_tbv_fat, mean_tbv_protein, animal.calves + ) + def _random_sample_with_replacement(self) -> AnimalPopulation: """Function to randomly sample the herd with replacement""" post_calves: list[Animal] = self._random_sample_with_replacement_by_type("calf") diff --git a/RUFAS/biophysical/animal/herd_manager.py b/RUFAS/biophysical/animal/herd_manager.py index 3aa9108da0..68bbdb0cf4 100644 --- a/RUFAS/biophysical/animal/herd_manager.py +++ b/RUFAS/biophysical/animal/herd_manager.py @@ -6,7 +6,7 @@ from RUFAS.biophysical.animal import animal_constants from RUFAS.biophysical.animal.animal import Animal from RUFAS.biophysical.animal.animal_config import AnimalConfig -from RUFAS.biophysical.animal.animal_genetics.animal_genetics import AnimalGenetics +from RUFAS.biophysical.animal.animal_genetics.animal_genetics import Genetics from RUFAS.biophysical.animal.animal_grouping_scenarios import AnimalGroupingScenario from RUFAS.biophysical.animal.animal_module_constants import AnimalModuleConstants from RUFAS.biophysical.animal.animal_module_reporter import AnimalModuleReporter @@ -350,6 +350,31 @@ def average_herd_305_days_milk_production(self) -> float: else 0.0 ) + @property + def all_animals(self) -> list[Animal]: + """ + Retrieve a combined list of all animals in the herd. + + Returns + ------- + list[Animal] + A list of all animals, including calves, heiferIs, heiferIIs, heiferIIIs, + and cows. + """ + return [*self.calves, *self.heiferIs, *self.heiferIIs, *self.heiferIIIs, *self.cows] + + @property + def animal_genetic_history_by_id(self) -> dict[int, str]: + """ + Retrieve a dict of genetic histories for all animals in the herd by id. + + Returns + ------- + dict[int, str] + A dict of genetic histories for all animals by id. + """ + return {animal.id: str(animal.genetic_history) for animal in self.all_animals} + def collect_daily_feed_request(self) -> RequestedFeed: """ Collects total amount of feeds needed for all animals on the current day. @@ -474,7 +499,7 @@ def _perform_daily_routines_for_animals( graduated_animals.append(animal) if animal_daily_routines_output.newborn_calf_config is not None: newborn_calf = self._create_newborn_calf( - animal_daily_routines_output.newborn_calf_config, simulation_day=time.simulation_day + animal_daily_routines_output.newborn_calf_config, time=time ) if newborn_calf.stillborn: stillborn_newborn_calves.append(newborn_calf) @@ -482,8 +507,20 @@ def _perform_daily_routines_for_animals( sold_newborn_calves.append(newborn_calf) else: newborn_calves.append(newborn_calf) + birth_year = Utility.back_track_birth_date(animal.days_born, time.current_date).year + mean_tbv_fat, mean_tbv_protein = Genetics.calculate_average_tbv( + [animal.genetics for animal in self.cows] + ) + animal.genetics.recalculate_values_at_lactation_start( + birth_year=birth_year, + animal_type=animal.animal_type, + parity=animal.calves, + group_specific_TBV_fat_mean=mean_tbv_fat, + group_specific_TBV_protein_mean=mean_tbv_protein, + ) elif animal_daily_routines_output.animal_status in [AnimalStatus.DEAD, AnimalStatus.SOLD]: sold_animals.append(animal) + animal.update_genetic_history(simulation_day=time.simulation_day) return (graduated_animals, sold_animals, stillborn_newborn_calves, newborn_calves, sold_newborn_calves) def _update_herd_structure( @@ -629,9 +666,36 @@ def daily_routines( AnimalModuleReporter.report_milk(self.daily_milk_report, time.simulation_day) AnimalModuleReporter.report_305d_milk(self.average_herd_305_days_milk_production) self._report_ration(time.simulation_day) + self._calculate_and_report_average_genetics(time.simulation_day) return herd_manager_output + def _calculate_and_report_average_genetics(self, simulation_day: int) -> None: + """ + Calculates and reports the average genetics for the herd, calves, heiferIs, heiferIIs, heiferIIIs, and cows. + """ + herd_average_genetics = Genetics.calculate_average_genetic_values( + [animal.genetics for animal in self.all_animals] + ) + AnimalModuleReporter.report_average_genetics(herd_average_genetics, "herd", simulation_day) + + calf_average_genetics = Genetics.calculate_average_genetic_values([animal.genetics for animal in self.calves]) + AnimalModuleReporter.report_average_genetics(calf_average_genetics, "calves", simulation_day) + heiferI_average_genetics = Genetics.calculate_average_genetic_values( + [animal.genetics for animal in self.heiferIs] + ) + AnimalModuleReporter.report_average_genetics(heiferI_average_genetics, "heiferI", simulation_day) + heiferII_average_genetics = Genetics.calculate_average_genetic_values( + [animal.genetics for animal in self.heiferIIs] + ) + AnimalModuleReporter.report_average_genetics(heiferII_average_genetics, "heiferII", simulation_day) + heiferIII_average_genetics = Genetics.calculate_average_genetic_values( + [animal.genetics for animal in self.heiferIIIs] + ) + AnimalModuleReporter.report_average_genetics(heiferIII_average_genetics, "heiferIII", simulation_day) + cow_average_genetics = Genetics.calculate_average_genetic_values([animal.genetics for animal in self.cows]) + AnimalModuleReporter.report_average_genetics(cow_average_genetics, "cow", simulation_day) + def _report_ration(self, simulation_day: int) -> None: """Report the ration for all pens.""" herd_total_ration: dict[str, float] = {} @@ -651,7 +715,7 @@ def _report_ration(self, simulation_day: int) -> None: AnimalModuleReporter.report_daily_herd_total_ration(herd_total_ration, simulation_day) - def _create_newborn_calf(self, newborn_calf_config: NewBornCalfValuesTypedDict, simulation_day: int) -> Animal: + def _create_newborn_calf(self, newborn_calf_config: NewBornCalfValuesTypedDict, time: RufasTime) -> Animal: """ Creates a new newborn calf instance and records its entry event in the herd if it is not sold. @@ -660,8 +724,8 @@ def _create_newborn_calf(self, newborn_calf_config: NewBornCalfValuesTypedDict, ---------- newborn_calf_config : NewBornCalfValuesTypedDict Configuration for the newborn calf containing its attributes. - simulation_day : int - The current day in the simulation. + time : RufasTime + The current time in the simulation. Returns ------- @@ -670,9 +734,13 @@ def _create_newborn_calf(self, newborn_calf_config: NewBornCalfValuesTypedDict, """ newborn_calf_config["id"] = AnimalPopulation.next_id() - newborn_calf: Animal = Animal(args=newborn_calf_config, simulation_day=simulation_day) + newborn_calf: Animal = Animal(args=newborn_calf_config, time=time) + mean_tbv_fat, mean_tbv_protein = Genetics.calculate_average_tbv([animal.genetics for animal in self.calves]) + newborn_calf.genetics.calculate_ebv_and_ranking_index( + newborn_calf.animal_type, mean_tbv_fat, mean_tbv_protein, newborn_calf.calves + ) if not (newborn_calf.sold or newborn_calf.stillborn): - newborn_calf.events.add_event(newborn_calf.days_born, simulation_day, animal_constants.ENTER_HERD) + newborn_calf.events.add_event(newborn_calf.days_born, time.simulation_day, animal_constants.ENTER_HERD) return newborn_calf def _check_if_heifers_need_to_be_sold( @@ -715,6 +783,7 @@ def _check_if_heifers_need_to_be_sold( cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history=str(removed_heiferIII.genetic_history), ) ) self.herd_statistics.sold_heiferIII_oversupply_num += 1 @@ -753,15 +822,25 @@ def _check_if_replacement_heifers_needed(self, time: RufasTime) -> list[Animal]: replacement.nutrients.total_phosphorus_in_animal = ( 0.0072 * replacement.body_weight * GeneralConstants.KG_TO_GRAMS ) - replacement_birth_date = time.current_date.date() - timedelta(days=replacement.days_born) - replacement.net_merit = AnimalGenetics.assign_net_merit_value_to_animals_entering_herd( - replacement_birth_date.strftime("%Y-%m-%d"), replacement.breed - ) + self._update_replacement_animal_genetics(replacement, time) animals_added.append(replacement) self.herd_statistics.bought_heifer_num += 1 return animals_added + def _update_replacement_animal_genetics(self, replacement: Animal, time: RufasTime) -> None: + """ + Updates the genetic values of a replacement animal. + """ + mean_tbv_fat, mean_tbv_protein = Genetics.calculate_average_tbv([animal.genetics for animal in self.heiferIIIs]) + replacement_birth_date = time.current_date.date() - timedelta(days=replacement.days_born) + replacement.genetics = Genetics( + birth_year=replacement_birth_date.year, animal_type=replacement.animal_type, parity=replacement.calves + ) + replacement.genetics.calculate_ebv_and_ranking_index( + replacement.animal_type, mean_tbv_fat, mean_tbv_protein, replacement.calves + ) + def _remove_animal_from_current_array(self, animal: Animal) -> None: """ Remove an animal object from the current array that it belongs to. @@ -1822,6 +1901,7 @@ def _update_sold_and_died_cow_statistics(self, sold_and_died_cows: list[Animal]) cull_reason=cow.cull_reason, days_in_milk=cow.days_in_milk, parity=cow.reproduction.calves, + genetic_history=str(cow.genetic_history), ) for cow in sold_and_died_cows ] @@ -1840,6 +1920,7 @@ def _update_sold_and_died_cow_statistics(self, sold_and_died_cows: list[Animal]) cull_reason=cow.cull_reason, days_in_milk=cow.days_in_milk, parity=cow.reproduction.calves, + genetic_history=str(cow.genetic_history), ) for cow in sold_cows ] @@ -1884,6 +1965,7 @@ def _update_sold_heiferII_statistics(self, sold_heiferIIs: list[Animal]) -> None cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history=str(heiferII.genetic_history), ) for heiferII in sold_heiferIIs ] @@ -1915,6 +1997,7 @@ def _update_sold_newborn_calf_statistics(self, sold_newborn_calves: list[Animal] cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history=str(calf.genetic_history) if calf.genetic_history else "NA", ) for calf in sold_newborn_calves ] diff --git a/RUFAS/biophysical/animal/reproduction/reproduction.py b/RUFAS/biophysical/animal/reproduction/reproduction.py index c7f3d1fa5a..bc2997555e 100644 --- a/RUFAS/biophysical/animal/reproduction/reproduction.py +++ b/RUFAS/biophysical/animal/reproduction/reproduction.py @@ -1,3 +1,4 @@ +from sys import maxsize import math import random from math import floor @@ -7,7 +8,6 @@ from RUFAS.biophysical.animal import animal_constants from RUFAS.biophysical.animal.animal_config import AnimalConfig -from RUFAS.biophysical.animal.animal_genetics.animal_genetics import AnimalGenetics from RUFAS.biophysical.animal.data_types.animal_enums import Breed from RUFAS.biophysical.animal.data_types.animal_typed_dicts import NewBornCalfValuesTypedDict from RUFAS.biophysical.animal.data_types.animal_types import AnimalType @@ -180,7 +180,8 @@ def reproduction_update(self, reproduction_inputs: ReproductionInputs, time: Ruf days_in_pregnancy=reproduction_inputs.days_in_pregnancy, days_in_milk=reproduction_inputs.days_in_milk, events=AnimalEvents(), - net_merit=reproduction_inputs.net_merit, + dam_tbv_fat=reproduction_inputs.dam_tbv_fat, + dam_tbv_protein=reproduction_inputs.dam_tbv_protein, phosphorus_for_gestation_required_for_calf=reproduction_inputs.phosphorus_for_gestation_required_for_calf, herd_reproduction_statistics=HerdReproductionStatistics(), newborn_calf_config=None, @@ -450,9 +451,6 @@ def cow_give_birth( reproduction_data_stream = self._simulate_estrus_if_eligible(reproduction_data_stream, time.simulation_day) - newborn_calf_net_merit = AnimalGenetics.assign_net_merit_value_to_newborn_calf( - time, reproduction_data_stream.breed, reproduction_data_stream.net_merit - ) reproduction_data_stream.newborn_calf_config = NewBornCalfValuesTypedDict( breed=reproduction_data_stream.breed.name, animal_type=AnimalType.CALF.value, @@ -460,7 +458,8 @@ def cow_give_birth( days_born=0, birth_weight=self.calf_birth_weight, initial_phosphorus=reproduction_data_stream.phosphorus_for_gestation_required_for_calf, - net_merit=newborn_calf_net_merit, + dam_tbv_fat=reproduction_data_stream.dam_tbv_fat, + dam_tbv_protein=reproduction_data_stream.dam_tbv_protein, ) return reproduction_data_stream @@ -528,7 +527,7 @@ def _simulate_first_estrus( simulation_day: int, estrus_note: str, avg_estrus_cycle: float, - max_cycle_length: float = math.inf, + max_cycle_length: int = maxsize, ) -> ReproductionDataStream: """ Calculate and set first next estrus day for an heiferII. @@ -543,7 +542,7 @@ def _simulate_first_estrus( Note explaining the reason for estrus simulation. avg_estrus_cycle : float Average length of the estrus cycle. - max_cycle_length : float, optional + max_cycle_length : int, optional Maximum allowable length for the estrus cycle, by default inf. Returns diff --git a/RUFAS/input_manager.py b/RUFAS/input_manager.py index 361f5e0466..497c93cf1b 100644 --- a/RUFAS/input_manager.py +++ b/RUFAS/input_manager.py @@ -30,7 +30,7 @@ "config", "animal", "animal_population", - "animal_net_merit", + "animal_mean_phenotype", "animal_top_listing_semen", "lactation", "economy", diff --git a/RUFAS/simulation_engine.py b/RUFAS/simulation_engine.py index 9e11708a5f..d5dac8c308 100644 --- a/RUFAS/simulation_engine.py +++ b/RUFAS/simulation_engine.py @@ -76,6 +76,7 @@ def simulate(self) -> None: self.time, self.herd_manager.heiferII_events_by_id, self.herd_manager.cow_events_by_id, + self.herd_manager.animal_genetic_history_by_id, ) EEEManager.estimate_all() t_end_sim = timer.time() diff --git a/RUFAS/util.py b/RUFAS/util.py index f45edd677a..d99034b972 100644 --- a/RUFAS/util.py +++ b/RUFAS/util.py @@ -4,6 +4,7 @@ import re import shutil from copy import deepcopy +from datetime import timedelta from pathlib import Path from random import random from typing import Any, Callable, Dict, List, Optional, Tuple @@ -669,6 +670,44 @@ def generate_random_number(mean: float, std_dev: float) -> float: """Generates a normally distributed random number using the provided mean and standard deviation.""" return np.random.normal(mean, std_dev) + @staticmethod + def generate_bivariate_random_numbers( + mu_x: float, mu_y: float, sigma_x: float, sigma_y: float, rho: float + ) -> tuple[float, float]: + """ + Generates multivariate random numbers based on provided parameters. + + This method generates two correlated random numbers from a bivariate + normal distribution, using the specified means, standard deviations, + and correlation coefficient. + + Parameters + ---------- + mu_x : float + Mean of the first random variable. + mu_y : float + Mean of the second random variable. + sigma_x : float + Standard deviation of the first random variable. + sigma_y : float + Standard deviation of the second random variable. + rho : float + Correlation coefficient between the two random variables. + + Returns + ------- + tuple[float, float] + A tuple containing two correlated random numbers generated + from the bivariate normal distribution. + """ + if sigma_x <= 0 or sigma_y <= 0: + raise ValueError("The standard deviations for a bivariate distribution must be positive.") + if not (-1.0 <= rho <= 1.0): + raise ValueError("The correlation coefficient for a bivariate distribution must be between -1 and 1.") + mean = [mu_x, mu_y] + cov = [[sigma_x**2, rho * sigma_x * sigma_y], [rho * sigma_x * sigma_y, sigma_y**2]] + return tuple(np.random.multivariate_normal(mean, cov)) + @staticmethod def flatten_dictionary( input_dictionary: dict[str, Any], parent_key: str = "", separator: str = "." @@ -971,6 +1010,26 @@ def get_date_formatter(date_format: str | None) -> DateFormatter: return DateFormatter(date_format) + @staticmethod + def back_track_birth_date(days_born: int, current_date: datetime.datetime) -> datetime.datetime: + """ + Calculates the birth date by subtracting a given number of days from the current date. + + Parameters + ---------- + days_born : int + The number of days since the animal's birth. + current_date : datetime.datetime + The current date from which the days will be subtracted. + + Returns + ------- + datetime.datetime + The calculated date of birth. + + """ + return current_date - timedelta(days_born) + class Aggregator: @staticmethod diff --git a/changelog.md b/changelog.md index 5c2b8b24f1..62eafe629c 100644 --- a/changelog.md +++ b/changelog.md @@ -341,6 +341,8 @@ v1.0.0 - [2728](https://github.com/RuminantFarmSystems/MASM/pull/2728) - [minor change] [InputManager] [NoInputChange] [NoOutputChange] Enforces the required file blobs. - [2743](https://github.com/RuminantFarmSystems/RuFaS/pull/2743) - [minor change] [NoInputChange] [NoOutputChange] Fix broken IM unit test. - [2744](https://github.com/RuminantFarmSystems/RuFaS/pull/2744) - [minor change] [NoInputChange] [NoOutputChange] Update the OM and RG wiki with new report filter options. +- [2734](https://github.com/RuminantFarmSystems/MASM/pull/2734) - [minor change] [InputChange][OutputChange][Animal] Implements the Genetics submodule. + ### v0.9.2 diff --git a/input/data/animal/animal_genetics/NetMerit_HO.csv b/input/data/animal/animal_genetics/NetMerit_HO.csv deleted file mode 100644 index 04f2855afb..0000000000 --- a/input/data/animal/animal_genetics/NetMerit_HO.csv +++ /dev/null @@ -1,59 +0,0 @@ -year_month,average,std -2006-02,57.974596,190.53950085123236 -2006-05,64.173146,188.6115217330656 -2006-08,84.630935,178.08442504055702 -2006-11,86.763831,182.97287215651238 -2007-02,94.058362,186.64063286936465 -2007-05,104.253542,184.97248472260475 -2007-08,96.521285,178.3898130245917 -2008-01,108.275404,176.0315512646434 -2008-04,108.139418,175.72124232039016 -2008-08,125.954896,182.93450706094018 -2009-01,137.945008,180.76218649894656 -2009-04,142.742302,181.46487208201145 -2009-08,149.801918,179.51665832875034 -2010-01,32.080236,199.78307254666075 -2010-04,37.527293,194.6824683454886 -2010-08,46.648118,194.84564715450554 -2010-12,53.252398,196.859138251821 -2011-04,68.132048,182.81341524988173 -2011-08,76.054593,186.1919756665264 -2011-12,84.693403,186.24327231145722 -2012-04,99.988792,190.9506317360085 -2012-08,108.02154,190.09990252503658 -2012-12,112.042317,189.58854691745364 -2013-04,132.521979,189.97862871103044 -2013-08,142.434448,190.17629581768406 -2013-12,153.371769,191.74361035197666 -2014-04,174.779201,187.49870440299472 -2014-08,186.797579,189.82049792037412 -2014-12,9.079418,195.16527666770352 -2015-04,29.411005,197.64056136808045 -2015-08,43.5462,201.43483395272034 -2015-12,53.902095,200.7894938377279 -2016-04,74.315331,205.43589862378101 -2016-08,81.337129,208.40773477737665 -2016-12,98.030341,211.27855662708345 -2017-04,120.137081,206.65197545099693 -2017-08,133.776755,210.50529145052374 -2017-12,159.840968,218.48832843615 -2018-04,165.349842,213.01573968271688 -2018-08,186.885319,225.43930020133632 -2018-12,203.958502,225.3560229989782 -2019-04,217.449203,227.96290467456498 -2019-08,232.664942,226.14003185667204 -2019-12,250.213756,229.68488380468676 -2020-04,36.931108,239.43821032552916 -2020-08,51.71824,233.29549959933297 -2020-12,67.14513,238.4551655034612 -2021-04,91.328729,245.57841426364118 -2021-08,130.637185,306.1845619594099 -2021-12,144.61792,313.8841273987482 -2022-04,170.722282,323.88013733279865 -2022-08,198.053415,332.7844850437559 -2022-12,218.543242,333.9914991195576 -2023-04,249.42981,338.80938128594363 -2023-08,273.408704,340.6332091605873 -2023-12,296.231263,345.74939244259684 -2024-04,322.6829,353.4989587814793 -2024-08,360.731239,364.4961663811087 diff --git a/input/data/animal/animal_genetics/TopListingSemen_HO.csv b/input/data/animal/animal_genetics/TopListingSemen_HO.csv deleted file mode 100644 index de47e77173..0000000000 --- a/input/data/animal/animal_genetics/TopListingSemen_HO.csv +++ /dev/null @@ -1,229 +0,0 @@ -year_month,estimated_PTA -2006-01,-630.0692402 -2006-02,-620.7267157 -2006-03,-611.3841912 -2006-04,-602.0416667 -2006-05,-592.6991422 -2006-06,-583.3566176 -2006-07,-574.0140931 -2006-08,-564.6715686 -2006-09,-555.3290441 -2006-10,-545.9865196 -2006-11,-536.6439951 -2006-12,-527.3014706 -2007-01,-517.9589461 -2007-02,-508.6164216 -2007-03,-499.2738971 -2007-04,-489.9313725 -2007-05,-480.588848 -2007-06,-471.2463235 -2007-07,-461.903799 -2007-08,-452.5612745 -2007-09,-443.21875 -2007-10,-433.8762255 -2007-11,-424.533701 -2007-12,-415.1911765 -2008-01,-405.848652 -2008-02,-396.5061275 -2008-03,-387.1636029 -2008-04,-377.8210784 -2008-05,-368.4785539 -2008-06,-359.1360294 -2008-07,-349.7935049 -2008-08,-340.4509804 -2008-09,-331.1084559 -2008-10,-321.7659314 -2008-11,-312.4234069 -2008-12,-303.0808824 -2009-01,-293.7383578 -2009-02,-284.3958333 -2009-03,-275.0533088 -2009-04,-265.7107843 -2009-05,-256.3682598 -2009-06,-247.0257353 -2009-07,-237.6832108 -2009-08,-228.3406863 -2009-09,-218.9981618 -2009-10,-209.6556373 -2009-11,-200.3131127 -2009-12,-190.9705882 -2010-01,-181.6280637 -2010-02,-172.2855392 -2010-03,-162.9430147 -2010-04,-153.6004902 -2010-05,-144.2579657 -2010-06,-134.9154412 -2010-07,-125.5729167 -2010-08,-116.2303922 -2010-09,-106.8878676 -2010-10,-97.54534314 -2010-11,-88.20281863 -2010-12,-78.86029412 -2011-01,-69.51776961 -2011-02,-60.1752451 -2011-03,-50.83272059 -2011-04,-41.49019608 -2011-05,-32.14767157 -2011-06,-22.80514706 -2011-07,-13.46262255 -2011-08,-4.120098039 -2011-09,5.222426471 -2011-10,14.56495098 -2011-11,23.90747549 -2011-12,33.25 -2012-01,42.59252451 -2012-02,51.93504902 -2012-03,61.27757353 -2012-04,70.62009804 -2012-05,79.96262255 -2012-06,89.30514706 -2012-07,98.64767157 -2012-08,107.9901961 -2012-09,117.3327206 -2012-10,126.6752451 -2012-11,136.0177696 -2012-12,145.3602941 -2013-01,154.7028186 -2013-02,164.0453431 -2013-03,173.3878676 -2013-04,182.7303922 -2013-05,192.0729167 -2013-06,201.4154412 -2013-07,210.7579657 -2013-08,220.1004902 -2013-09,229.4430147 -2013-10,238.7855392 -2013-11,248.1280637 -2013-12,257.4705882 -2014-01,266.8131127 -2014-02,276.1556373 -2014-03,285.4981618 -2014-04,294.8406863 -2014-05,304.1832108 -2014-06,313.5257353 -2014-07,322.8682598 -2014-08,332.2107843 -2014-09,341.5533088 -2014-10,350.8958333 -2014-11,360.2383578 -2014-12,369.5808824 -2015-01,378.9234069 -2015-02,388.2659314 -2015-03,397.6084559 -2015-04,406.9509804 -2015-05,416.2935049 -2015-06,425.6360294 -2015-07,434.9785539 -2015-08,444.3210784 -2015-09,453.6636029 -2015-10,463.0061275 -2015-11,472.348652 -2015-12,481.6911765 -2016-01,491.033701 -2016-02,500.3762255 -2016-03,509.71875 -2016-04,519.0612745 -2016-05,528.403799 -2016-06,537.7463235 -2016-07,547.088848 -2016-08,556.4313725 -2016-09,565.7738971 -2016-10,575.1164216 -2016-11,584.4589461 -2016-12,593.8014706 -2017-01,603.1439951 -2017-02,612.4865196 -2017-03,621.8290441 -2017-04,631.1715686 -2017-05,640.5140931 -2017-06,649.8566176 -2017-07,659.1991422 -2017-08,668.5416667 -2017-09,677.8841912 -2017-10,687.2267157 -2017-11,696.5692402 -2017-12,705.9117647 -2018-01,715.2542892 -2018-02,724.5968137 -2018-03,733.9393382 -2018-04,743.2818627 -2018-05,752.6243873 -2018-06,761.9669118 -2018-07,771.3094363 -2018-08,780.6519608 -2018-09,789.9944853 -2018-10,799.3370098 -2018-11,808.6795343 -2018-12,818.0220588 -2019-01,827.3645833 -2019-02,836.7071078 -2019-03,846.0496324 -2019-04,855.3921569 -2019-05,864.7346814 -2019-06,874.0772059 -2019-07,883.4197304 -2019-08,892.7622549 -2019-09,902.1047794 -2019-10,911.4473039 -2019-11,920.7898284 -2019-12,930.1323529 -2020-01,939.4748775 -2020-02,948.817402 -2020-03,958.1599265 -2020-04,967.502451 -2020-05,976.8449755 -2020-06,986.1875 -2020-07,995.5300245 -2020-08,1004.872549 -2020-09,1014.215074 -2020-10,1023.557598 -2020-11,1032.900123 -2020-12,1042.242647 -2021-01,1051.585172 -2021-02,1060.927696 -2021-03,1070.270221 -2021-04,1079.612745 -2021-05,1088.95527 -2021-06,1098.297794 -2021-07,1107.640319 -2021-08,1116.982843 -2021-09,1126.325368 -2021-10,1135.667892 -2021-11,1145.010417 -2021-12,1154.352941 -2022-01,1163.695466 -2022-02,1173.03799 -2022-03,1182.380515 -2022-04,1191.723039 -2022-05,1201.065564 -2022-06,1210.408088 -2022-07,1219.750613 -2022-08,1229.093137 -2022-09,1238.435662 -2022-10,1247.778186 -2022-11,1257.120711 -2022-12,1266.463235 -2023-01,1275.80576 -2023-02,1285.148284 -2023-03,1294.490809 -2023-04,1303.833333 -2023-05,1313.175858 -2023-06,1322.518382 -2023-07,1331.860907 -2023-08,1341.203431 -2023-09,1350.545956 -2023-10,1359.88848 -2023-11,1369.231005 -2023-12,1378.573529 -2024-01,1387.916054 -2024-02,1397.258578 -2024-03,1406.601103 -2024-04,1415.943627 -2024-05,1425.286152 -2024-06,1434.628676 -2024-07,1443.971201 -2024-08,1453.313725 -2024-09,1462.65625 -2024-10,1471.998775 -2024-11,1481.341299 -2024-12,1490.683824 \ No newline at end of file diff --git a/input/data/animal_genetics/TopListingSemen_HO.csv b/input/data/animal_genetics/TopListingSemen_HO.csv new file mode 100644 index 0000000000..fd0ed12965 --- /dev/null +++ b/input/data/animal_genetics/TopListingSemen_HO.csv @@ -0,0 +1,257 @@ +year_month,estimated_protein,estimated_fat +2004-09,-99.45339286,-110.1192857 +2004-10,-98.48710317,-108.5872782 +2004-11,-97.52081349,-107.0552708 +2004-12,-96.55452381,-105.5232633 +2005-01,-95.58823413,-103.9912558 +2005-02,-94.62194444,-102.4592484 +2005-03,-93.65565476,-100.9272409 +2005-04,-92.68936508,-99.39523343 +2005-05,-91.7230754,-97.86322596 +2005-06,-90.75678571,-96.33121849 +2005-07,-89.79049603,-94.79921102 +2005-08,-88.82420635,-93.26720355 +2005-09,-87.85791667,-91.73519608 +2005-10,-86.89162698,-90.20318861 +2005-11,-85.9253373,-88.67118114 +2005-12,-84.95904762,-87.13917367 +2006-01,-83.99275794,-85.6071662 +2006-02,-83.02646825,-84.07515873 +2006-03,-82.06017857,-82.54315126 +2006-04,-81.09388889,-81.01114379 +2006-05,-80.12759921,-79.47913632 +2006-06,-79.16130952,-77.94712885 +2006-07,-78.19501984,-76.41512138 +2006-08,-77.22873016,-74.88311391 +2006-09,-76.26244048,-73.35110644 +2006-10,-75.29615079,-71.81909897 +2006-11,-74.32986111,-70.2870915 +2006-12,-73.36357143,-68.75508403 +2007-01,-72.39728175,-67.22307656 +2007-02,-71.43099206,-65.69106909 +2007-03,-70.46470238,-64.15906162 +2007-04,-69.4984127,-62.62705415 +2007-05,-68.53212302,-61.09504669 +2007-06,-67.56583333,-59.56303922 +2007-07,-66.59954365,-58.03103175 +2007-08,-65.63325397,-56.49902428 +2007-09,-64.66696429,-54.96701681 +2007-10,-63.7006746,-53.43500934 +2007-11,-62.73438492,-51.90300187 +2007-12,-61.76809524,-50.3709944 +2008-01,-60.80180556,-48.83898693 +2008-02,-59.83551587,-47.30697946 +2008-03,-58.86922619,-45.77497199 +2008-04,-57.90293651,-44.24296452 +2008-05,-56.93664683,-42.71095705 +2008-06,-55.97035714,-41.17894958 +2008-07,-55.00406746,-39.64694211 +2008-08,-54.03777778,-38.11493464 +2008-09,-53.0714881,-36.58292717 +2008-10,-52.10519841,-35.0509197 +2008-11,-51.13890873,-33.51891223 +2008-12,-50.17261905,-31.98690476 +2009-01,-49.20632937,-30.45489729 +2009-02,-48.24003968,-28.92288982 +2009-03,-47.27375,-27.39088235 +2009-04,-46.30746032,-25.85887488 +2009-05,-45.34117063,-24.32686741 +2009-06,-44.37488095,-22.79485994 +2009-07,-43.40859127,-21.26285247 +2009-08,-42.44230159,-19.730845 +2009-09,-41.4760119,-18.19883754 +2009-10,-40.50972222,-16.66683007 +2009-11,-39.54343254,-15.1348226 +2009-12,-38.57714286,-13.60281513 +2010-01,-37.61085317,-12.07080766 +2010-02,-36.64456349,-10.53880019 +2010-03,-35.67827381,-9.006792717 +2010-04,-34.71198413,-7.474785247 +2010-05,-33.74569444,-5.942777778 +2010-06,-32.77940476,-4.410770308 +2010-07,-31.81311508,-2.878762838 +2010-08,-30.8468254,-1.346755369 +2010-09,-29.88053571,0.1852521008 +2010-10,-28.91424603,1.71725957 +2010-11,-27.94795635,3.24926704 +2010-12,-26.98166667,4.78127451 +2011-01,-26.01537698,6.313281979 +2011-02,-25.0490873,7.845289449 +2011-03,-24.08279762,9.377296919 +2011-04,-23.11650794,10.90930439 +2011-05,-22.15021825,12.44131186 +2011-06,-21.18392857,13.97331933 +2011-07,-20.21763889,15.5053268 +2011-08,-19.25134921,17.03733427 +2011-09,-18.28505952,18.56934174 +2011-10,-17.31876984,20.10134921 +2011-11,-16.35248016,21.63335668 +2011-12,-15.38619048,23.16536415 +2012-01,-14.41990079,24.69737162 +2012-02,-13.45361111,26.22937908 +2012-03,-12.48732143,27.76138655 +2012-04,-11.52103175,29.29339402 +2012-05,-10.55474206,30.82540149 +2012-06,-9.588452381,32.35740896 +2012-07,-8.622162698,33.88941643 +2012-08,-7.655873016,35.4214239 +2012-09,-6.689583333,36.95343137 +2012-10,-5.723293651,38.48543884 +2012-11,-4.757003968,40.01744631 +2012-12,-3.790714286,41.54945378 +2013-01,-2.824424603,43.08146125 +2013-02,-1.858134921,44.61346872 +2013-03,-0.8918452381,46.14547619 +2013-04,0.07444444444,47.67748366 +2013-05,1.040734127,49.20949113 +2013-06,2.00702381,50.7414986 +2013-07,2.973313492,52.27350607 +2013-08,3.939603175,53.80551354 +2013-09,4.905892857,55.33752101 +2013-10,5.87218254,56.86952848 +2013-11,6.838472222,58.40153595 +2013-12,7.804761905,59.93354342 +2014-01,8.771051587,61.46555089 +2014-02,9.73734127,62.99755836 +2014-03,10.70363095,64.52956583 +2014-04,11.66992063,66.0615733 +2014-05,12.63621032,67.59358077 +2014-06,13.6025,69.12558824 +2014-07,14.56878968,70.6575957 +2014-08,15.53507937,72.18960317 +2014-09,16.50136905,73.72161064 +2014-10,17.46765873,75.25361811 +2014-11,18.43394841,76.78562558 +2014-12,19.4002381,78.31763305 +2015-01,20.36652778,79.84964052 +2015-02,21.33281746,81.38164799 +2015-03,22.29910714,82.91365546 +2015-04,23.26539683,84.44566293 +2015-05,24.23168651,85.9776704 +2015-06,25.19797619,87.50967787 +2015-07,26.16426587,89.04168534 +2015-08,27.13055556,90.57369281 +2015-09,28.09684524,92.10570028 +2015-10,29.06313492,93.63770775 +2015-11,30.0294246,95.16971522 +2015-12,30.99571429,96.70172269 +2016-01,31.96200397,98.23373016 +2016-02,32.92829365,99.76573763 +2016-03,33.89458333,101.2977451 +2016-04,34.86087302,102.8297526 +2016-05,35.8271627,104.36176 +2016-06,36.79345238,105.8937675 +2016-07,37.75974206,107.425775 +2016-08,38.72603175,108.9577824 +2016-09,39.69232143,110.4897899 +2016-10,40.65861111,112.0217974 +2016-11,41.62490079,113.5538049 +2016-12,42.59119048,115.0858123 +2017-01,43.55748016,116.6178198 +2017-02,44.52376984,118.1498273 +2017-03,45.49005952,119.6818347 +2017-04,46.45634921,121.2138422 +2017-05,47.42263889,122.7458497 +2017-06,48.38892857,124.2778571 +2017-07,49.35521825,125.8098646 +2017-08,50.32150794,127.3418721 +2017-09,51.28779762,128.8738796 +2017-10,52.2540873,130.405887 +2017-11,53.22037698,131.9378945 +2017-12,54.18666667,133.469902 +2018-01,55.15295635,135.0019094 +2018-02,56.11924603,136.5339169 +2018-03,57.08553571,138.0659244 +2018-04,58.0518254,139.5979318 +2018-05,59.01811508,141.1299393 +2018-06,59.98440476,142.6619468 +2018-07,60.95069444,144.1939542 +2018-08,61.91698413,145.7259617 +2018-09,62.88327381,147.2579692 +2018-10,63.84956349,148.7899767 +2018-11,64.81585317,150.3219841 +2018-12,65.78214286,151.8539916 +2019-01,66.74843254,153.3859991 +2019-02,67.71472222,154.9180065 +2019-03,68.6810119,156.450014 +2019-04,69.64730159,157.9820215 +2019-05,70.61359127,159.5140289 +2019-06,71.57988095,161.0460364 +2019-07,72.54617063,162.5780439 +2019-08,73.51246032,164.1100514 +2019-09,74.47875,165.6420588 +2019-10,75.44503968,167.1740663 +2019-11,76.41132937,168.7060738 +2019-12,77.37761905,170.2380812 +2020-01,78.34390873,171.7700887 +2020-02,79.31019841,173.3020962 +2020-03,80.2764881,174.8341036 +2020-04,81.24277778,176.3661111 +2020-05,82.20906746,177.8981186 +2020-06,83.17535714,179.4301261 +2020-07,84.14164683,180.9621335 +2020-08,85.10793651,182.494141 +2020-09,86.07422619,184.0261485 +2020-10,87.04051587,185.5581559 +2020-11,88.00680556,187.0901634 +2020-12,88.97309524,188.6221709 +2021-01,89.93938492,190.1541783 +2021-02,90.9056746,191.6861858 +2021-03,91.87196429,193.2181933 +2021-04,92.83825397,194.7502007 +2021-05,93.80454365,196.2822082 +2021-06,94.77083333,197.8142157 +2021-07,95.73712302,199.3462232 +2021-08,96.7034127,200.8782306 +2021-09,97.66970238,202.4102381 +2021-10,98.63599206,203.9422456 +2021-11,99.60228175,205.474253 +2021-12,100.5685714,207.0062605 +2022-01,101.5348611,208.538268 +2022-02,102.5011508,210.0702754 +2022-03,103.4674405,211.6022829 +2022-04,104.4337302,213.1342904 +2022-05,105.4000198,214.6662979 +2022-06,106.3663095,216.1983053 +2022-07,107.3325992,217.7303128 +2022-08,108.2988889,219.2623203 +2022-09,109.2651786,220.7943277 +2022-10,110.2314683,222.3263352 +2022-11,111.1977579,223.8583427 +2022-12,112.1640476,225.3903501 +2023-01,113.1303373,226.9223576 +2023-02,114.096627,228.4543651 +2023-03,115.0629167,229.9863725 +2023-04,116.0292063,231.51838 +2023-05,116.995496,233.0503875 +2023-06,117.9617857,234.582395 +2023-07,118.9280754,236.1144024 +2023-08,119.8943651,237.6464099 +2023-09,120.8606548,239.1784174 +2023-10,121.8269444,240.7104248 +2023-11,122.7932341,242.2424323 +2023-12,123.7595238,243.7744398 +2024-01,124.7258135,245.3064472 +2024-02,125.6921032,246.8384547 +2024-03,126.6583929,248.3704622 +2024-04,127.6246825,249.9024697 +2024-05,128.5909722,251.4344771 +2024-06,129.5572619,252.9664846 +2024-07,130.5235516,254.4984921 +2024-08,131.4898413,256.0304995 +2024-09,132.456131,257.562507 +2024-10,133.4224206,259.0945145 +2024-11,134.3887103,260.6265219 +2024-12,135.355,262.1585294 +2025-01,136.3212897,263.6905369 +2025-02,137.2875794,265.2225444 +2025-03,138.253869,266.7545518 +2025-04,139.2201587,268.2865593 +2025-05,140.1864484,269.8185668 +2025-06,141.1527381,271.3505742 +2025-07,142.1190278,272.8825817 +2025-08,143.0853175,274.4145892 +2025-09,144.0516071,275.9465966 +2025-10,145.0178968,277.4786041 +2025-11,145.9841865,279.0106116 +2025-12,146.9504762,280.542619 \ No newline at end of file diff --git a/input/data/animal_genetics/mean_phenotype.csv b/input/data/animal_genetics/mean_phenotype.csv new file mode 100644 index 0000000000..dc313f6888 --- /dev/null +++ b/input/data/animal_genetics/mean_phenotype.csv @@ -0,0 +1,55 @@ +birth_year,fat_kg,protein_kg +2023,537.052928,409.593576 +2022,523.445168,400.521736 +2021,511.198184,397.800184 +2020,493.508096,391.449896 +2019,478.993152,385.099608 +2018,468.560536,379.202912 +2017,455.85996,371.94544 +2016,447.241712,366.955928 +2015,443.612976,366.048744 +2014,439.530648,364.234376 +2013,430.005216,358.791272 +2012,423.654928,355.162536 +2011,419.119008,350.173024 +2010,413.222312,345.183512 +2009,401.42892,339.286816 +2008,396.439408,335.204488 +2007,390.996304,332.482936 +2006,392.35708,331.12216 +2005,394.62504,332.482936 +2004,394.171448,331.12216 +2003,393.264264,330.214976 +2002,386.006792,325.225464 +2001,382.378056,321.596728 +2000,384.646016,322.05032 +1999,382.378056,320.689544 +1998,376.934952,316.607216 +1997,380.110096,316.607216 +1996,367.863112,306.1746 +1995,358.33768,296.649168 +1994,345.183512,286.670144 +1993,338.833224,279.866264 +1992,336.565264,279.866264 +1991,330.668568,274.42316 +1990,325.679056,268.526464 +1989,322.05032,263.536952 +1988,312.524888,258.093848 +1987,303.90664,250.836376 +1986,301.185088,245.846864 +1985,293.020432,240.40376 +1984,288.938104,239.950168 +1983,284.402184,237.228616 +1982,278.505488,231.33192 +1981,273.515976,228.156776 +1980,270.794424,229.517552 +1979,268.526464,227.249592 +1978,266.712096,229.06396 +1977,263.990544,231.33192 +1976,260.361808,229.517552 +1975,259.001032,227.249592 +1974,256.27948,217.270568 +1973,248.568416,208.198728 +1972,243.125312,209.105912 +1971,236.321432,203.209216 +1970,234.960656,211.827464 \ No newline at end of file diff --git a/input/metadata/end_to_end_testing/freestall_e2e_metadata.json b/input/metadata/end_to_end_testing/freestall_e2e_metadata.json index da04f16758..fa6604aa26 100644 --- a/input/metadata/end_to_end_testing/freestall_e2e_metadata.json +++ b/input/metadata/end_to_end_testing/freestall_e2e_metadata.json @@ -28,17 +28,17 @@ "type": "json", "properties": "animal_population_properties" }, - "animal_net_merit": { - "title": "Animal Net Merit data", - "description": "The net merit value for cows.", - "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "animal_mean_phenotype": { + "title": "Mean animal phenotype data by birth year", + "description": "The mean animal phenotype data by birth year.", + "path": "input/data/animal_genetics/mean_phenotype.csv", "type": "csv", - "properties": "animal_net_merit_properties" + "properties": "animal_mean_phenotype_properties" }, "animal_top_listing_semen": { "title": "Animal Top Listing Semen data", "description": "The top listing semen value for new born calves.", - "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "path": "input/data/animal_genetics/TopListingSemen_HO.csv", "type": "csv", "properties": "animal_top_listing_semen_properties" }, diff --git a/input/metadata/end_to_end_testing/no_animal_e2e_metadata.json b/input/metadata/end_to_end_testing/no_animal_e2e_metadata.json index e17b518619..2c1e46a188 100644 --- a/input/metadata/end_to_end_testing/no_animal_e2e_metadata.json +++ b/input/metadata/end_to_end_testing/no_animal_e2e_metadata.json @@ -28,17 +28,17 @@ "type": "json", "properties": "animal_population_properties" }, - "animal_net_merit": { - "title": "Animal Net Merit data", - "description": "The net merit value for cows.", - "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "animal_mean_phenotype": { + "title": "Mean animal phenotype data by birth year", + "description": "The mean animal phenotype data by birth year.", + "path": "input/data/animal_genetics/mean_phenotype.csv", "type": "csv", - "properties": "animal_net_merit_properties" + "properties": "animal_mean_phenotype_properties" }, "animal_top_listing_semen": { "title": "Animal Top Listing Semen data", "description": "The top listing semen value for new born calves.", - "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "path": "input/data/animal_genetics/TopListingSemen_HO.csv", "type": "csv", "properties": "animal_top_listing_semen_properties" }, diff --git a/input/metadata/end_to_end_testing/open_lot_e2e_metadata.json b/input/metadata/end_to_end_testing/open_lot_e2e_metadata.json index 745192a557..9bce45da44 100644 --- a/input/metadata/end_to_end_testing/open_lot_e2e_metadata.json +++ b/input/metadata/end_to_end_testing/open_lot_e2e_metadata.json @@ -28,17 +28,17 @@ "type": "json", "properties": "animal_population_properties" }, - "animal_net_merit": { - "title": "Animal Net Merit data", - "description": "The net merit value for cows.", - "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "animal_mean_phenotype": { + "title": "Mean animal phenotype data by birth year", + "description": "The mean animal phenotype data by birth year.", + "path": "input/data/animal_genetics/mean_phenotype.csv", "type": "csv", - "properties": "animal_net_merit_properties" + "properties": "animal_mean_phenotype_properties" }, "animal_top_listing_semen": { "title": "Animal Top Listing Semen data", "description": "The top listing semen value for new born calves.", - "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "path": "input/data/animal_genetics/TopListingSemen_HO.csv", "type": "csv", "properties": "animal_top_listing_semen_properties" }, diff --git a/input/metadata/example_freestall_dairy_metadata.json b/input/metadata/example_freestall_dairy_metadata.json index 3498b9c9de..c731832124 100644 --- a/input/metadata/example_freestall_dairy_metadata.json +++ b/input/metadata/example_freestall_dairy_metadata.json @@ -21,17 +21,17 @@ "type": "json", "properties": "animal_population_properties" }, - "animal_net_merit": { - "title": "Animal Net Merit data", - "description": "The net merit value for cows.", - "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "animal_mean_phenotype": { + "title": "Mean animal phenotype data by birth year", + "description": "The mean animal phenotype data by birth year.", + "path": "input/data/animal_genetics/mean_phenotype.csv", "type": "csv", - "properties": "animal_net_merit_properties" + "properties": "animal_mean_phenotype_properties" }, "animal_top_listing_semen": { "title": "Animal Top Listing Semen data", "description": "The top listing semen value for new born calves.", - "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "path": "input/data/animal_genetics/TopListingSemen_HO.csv", "type": "csv", "properties": "animal_top_listing_semen_properties" }, diff --git a/input/metadata/example_no_animal_metadata.json b/input/metadata/example_no_animal_metadata.json index d62fc9048c..eb31da8ab3 100644 --- a/input/metadata/example_no_animal_metadata.json +++ b/input/metadata/example_no_animal_metadata.json @@ -21,17 +21,17 @@ "type": "json", "properties": "animal_population_properties" }, - "animal_net_merit": { - "title": "Animal Net Merit data", - "description": "The net merit value for cows.", - "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "animal_mean_phenotype": { + "title": "Mean animal phenotype data by birth year", + "description": "The mean animal phenotype data by birth year.", + "path": "input/data/animal_genetics/mean_phenotype.csv", "type": "csv", - "properties": "animal_net_merit_properties" + "properties": "animal_mean_phenotype_properties" }, "animal_top_listing_semen": { "title": "Animal Top Listing Semen data", "description": "The top listing semen value for new born calves.", - "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "path": "input/data/animal_genetics/TopListingSemen_HO.csv", "type": "csv", "properties": "animal_top_listing_semen_properties" }, diff --git a/input/metadata/example_open_lot_metadata.json b/input/metadata/example_open_lot_metadata.json index 9d62c9c405..26c87f75a3 100644 --- a/input/metadata/example_open_lot_metadata.json +++ b/input/metadata/example_open_lot_metadata.json @@ -21,17 +21,17 @@ "type": "json", "properties": "animal_population_properties" }, - "animal_net_merit": { - "title": "Animal Net Merit data", - "description": "The net merit value for cows.", - "path": "input/data/animal/animal_genetics/NetMerit_HO.csv", + "animal_mean_phenotype": { + "title": "Mean animal phenotype data by birth year", + "description": "The mean animal phenotype data by birth year.", + "path": "input/data/animal_genetics/mean_phenotype.csv", "type": "csv", - "properties": "animal_net_merit_properties" + "properties": "animal_mean_phenotype_properties" }, "animal_top_listing_semen": { "title": "Animal Top Listing Semen data", "description": "The top listing semen value for new born calves.", - "path": "input/data/animal/animal_genetics/TopListingSemen_HO.csv", + "path": "input/data/animal_genetics/TopListingSemen_HO.csv", "type": "csv", "properties": "animal_top_listing_semen_properties" }, diff --git a/input/metadata/properties/default.json b/input/metadata/properties/default.json index 308ebf4150..82de56a5a2 100644 --- a/input/metadata/properties/default.json +++ b/input/metadata/properties/default.json @@ -1597,30 +1597,30 @@ } } }, - "animal_net_merit_properties": { + "animal_mean_phenotype_properties": { "data_collection_app_compatible": false, - "year_month": { + "birth_year": { "type": "array", - "description": "The birth year and month of the animal.", + "description": "The birth year of the animal.", "properties": { - "type": "string", - "description": "The birth year and month of the animal." + "type": "number", + "description": "The birth year of the animal." } }, - "average": { + "fat_kg": { "type": "array", - "description": "The average net-merit values of the animal.", + "description": "The average fat phenotype values of the animal.", "properties": { "type": "number", - "description": "The average net-merit values of the animal born on the given birth year and month." + "description": "The average fat phenotype values of the animal born in the given birth year." } }, - "std": { + "protein_kg": { "type": "array", - "description": "The standard deviation of the net-merit values of the animal.", + "description": "The mean protein phenotype values of the animal.", "properties": { "type": "number", - "description": "The standard deviation of the net-merit values of the animal born on the given birth year and month.", + "description": "The mean protein phenotype values of the animal born in the given birth year.", "minimum": 0 } } @@ -1635,12 +1635,20 @@ "description": "The birth year and month of the baby calf." } }, - "estimated_PTA": { + "estimated_protein": { + "type": "array", + "description": "The estimated protein value for the top listing semen.", + "properties": { + "type": "number", + "description": "The estimated protein value for the top listing semen." + } + }, + "estimated_fat": { "type": "array", - "description": "The estimated semen_predicted_transmitting_ability value for the top listing semen.", + "description": "The estimated fat value for the top listing semen.", "properties": { "type": "number", - "description": "The estimated semen_predicted_transmitting_ability value for the top listing semen." + "description": "The estimated fat value for the top listing semen." } } }, diff --git a/tests/test_biophysical/test_animal/animal_genetics/test_animal_genetics.py b/tests/test_biophysical/test_animal/animal_genetics/test_animal_genetics.py index 965976f861..b20841bd1b 100644 --- a/tests/test_biophysical/test_animal/animal_genetics/test_animal_genetics.py +++ b/tests/test_biophysical/test_animal/animal_genetics/test_animal_genetics.py @@ -1,1276 +1,333 @@ -from typing import Tuple from unittest.mock import call -import numpy as np +import numpy import pytest -from pytest_mock import MockerFixture - -from RUFAS.biophysical.animal.animal_genetics.animal_genetics import AnimalGenetics -from RUFAS.biophysical.animal.data_types.animal_enums import Breed -from RUFAS.input_manager import InputManager -from RUFAS.rufas_time import RufasTime -from RUFAS.output_manager import OutputManager from RUFAS.util import Utility +from pytest_mock import MockerFixture +from RUFAS.biophysical.animal.animal_config import AnimalConfig +from RUFAS.biophysical.animal.animal_genetics.animal_genetics import ( + Genetics, + TBV_CORRELATION, + TBV_FAT_STD, + TBV_PROTEIN_STD, + E_PERMANENT_FAT_STD, + E_PERMANENT_PROTEIN_STD, + E_PERMANENT_CORRELATION, + E_TEMPORARY_FAT_STD, + E_TEMPORARY_PROTEIN_STD, + E_TEMPORARY_CORRELATION, +) +from RUFAS.biophysical.animal.data_types.animal_types import AnimalType -def setup_animal_genetics(mocker: MockerFixture) -> None: - im = InputManager() - mocker.patch("RUFAS.input_manager.InputManager.__init__", return_value=None) - mocker.patch.object( - im, - "get_data", - side_effect=[ - { - "year_month": [ - "2006-02", - "2006-05", - "2006-08", - "2006-11", - "2007-02", - "2007-05", - "2007-08", - "2008-01", - "2008-04", - "2008-08", - "2009-01", - "2009-04", - "2009-08", - "2010-01", - "2010-04", - "2010-08", - "2010-12", - "2011-04", - "2011-08", - "2011-12", - "2012-04", - "2012-08", - "2012-12", - "2013-04", - "2013-08", - "2013-12", - "2014-04", - "2014-08", - "2014-12", - "2015-04", - "2015-08", - "2015-12", - "2016-04", - "2016-08", - "2016-12", - "2017-04", - "2017-08", - "2017-12", - "2018-04", - "2018-08", - "2018-12", - "2019-04", - "2019-08", - "2019-12", - "2020-04", - "2020-08", - "2020-12", - "2021-04", - "2021-08", - "2021-12", - "2022-04", - "2022-08", - "2022-12", - "2023-04", - "2023-08", - "2023-12", - "2024-04", - "2024-08", - ], - "average": [ - 57.974596, - 64.173146, - 84.630935, - 86.763831, - 94.058362, - 104.253542, - 96.521285, - 108.275404, - 108.139418, - 125.954896, - 137.945008, - 142.742302, - 149.801918, - 32.080236, - 37.527293, - 46.648118, - 53.252398, - 68.132048, - 76.054593, - 84.693403, - 99.988792, - 108.02154, - 112.042317, - 132.521979, - 142.434448, - 153.371769, - 174.779201, - 186.797579, - 9.079418, - 29.411005, - 43.5462, - 53.902095, - 74.315331, - 81.337129, - 98.030341, - 120.137081, - 133.776755, - 159.840968, - 165.349842, - 186.885319, - 203.958502, - 217.449203, - 232.664942, - 250.213756, - 36.931108, - 51.71824, - 67.14513, - 91.328729, - 130.637185, - 144.61792, - 170.722282, - 198.053415, - 218.543242, - 249.42981, - 273.408704, - 296.231263, - 322.6829, - 360.731239, - ], - "std": [ - 190.5395008512324, - 188.6115217330656, - 178.08442504055702, - 182.9728721565124, - 186.64063286936465, - 184.97248472260475, - 178.3898130245917, - 176.0315512646434, - 175.72124232039016, - 182.93450706094015, - 180.76218649894656, - 181.46487208201145, - 179.51665832875034, - 199.78307254666075, - 194.6824683454886, - 194.84564715450557, - 196.859138251821, - 182.81341524988173, - 186.1919756665264, - 186.2432723114572, - 190.9506317360085, - 190.09990252503655, - 189.58854691745364, - 189.97862871103044, - 190.1762958176841, - 191.74361035197663, - 187.49870440299472, - 189.82049792037407, - 195.16527666770352, - 197.64056136808043, - 201.43483395272037, - 200.7894938377279, - 205.435898623781, - 208.40773477737665, - 211.27855662708345, - 206.65197545099693, - 210.5052914505237, - 218.48832843615, - 213.01573968271688, - 225.43930020133632, - 225.3560229989782, - 227.96290467456495, - 226.14003185667204, - 229.6848838046868, - 239.4382103255292, - 233.29549959933297, - 238.4551655034612, - 245.5784142636412, - 306.1845619594099, - 313.8841273987482, - 323.88013733279865, - 332.7844850437559, - 333.9914991195576, - 338.80938128594363, - 340.6332091605873, - 345.74939244259684, - 353.4989587814793, - 364.4961663811087, - ], - }, - { - "year_month": [ - "2006-01", - "2006-02", - "2006-03", - "2006-04", - "2006-05", - "2006-06", - "2006-07", - "2006-08", - "2006-09", - "2006-10", - "2006-11", - "2006-12", - "2007-01", - "2007-02", - "2007-03", - "2007-04", - "2007-05", - "2007-06", - "2007-07", - "2007-08", - "2007-09", - "2007-10", - "2007-11", - "2007-12", - "2008-01", - "2008-02", - "2008-03", - "2008-04", - "2008-05", - "2008-06", - "2008-07", - "2008-08", - "2008-09", - "2008-10", - "2008-11", - "2008-12", - "2009-01", - "2009-02", - "2009-03", - "2009-04", - "2009-05", - "2009-06", - "2009-07", - "2009-08", - "2009-09", - "2009-10", - "2009-11", - "2009-12", - "2010-01", - "2010-02", - "2010-03", - "2010-04", - "2010-05", - "2010-06", - "2010-07", - "2010-08", - "2010-09", - "2010-10", - "2010-11", - "2010-12", - "2011-01", - "2011-02", - "2011-03", - "2011-04", - "2011-05", - "2011-06", - "2011-07", - "2011-08", - "2011-09", - "2011-10", - "2011-11", - "2011-12", - "2012-01", - "2012-02", - "2012-03", - "2012-04", - "2012-05", - "2012-06", - "2012-07", - "2012-08", - "2012-09", - "2012-10", - "2012-11", - "2012-12", - "2013-01", - "2013-02", - "2013-03", - "2013-04", - "2013-05", - "2013-06", - "2013-07", - "2013-08", - "2013-09", - "2013-10", - "2013-11", - "2013-12", - "2014-01", - "2014-02", - "2014-03", - "2014-04", - "2014-05", - "2014-06", - "2014-07", - "2014-08", - "2014-09", - "2014-10", - "2014-11", - "2014-12", - "2015-01", - "2015-02", - "2015-03", - "2015-04", - "2015-05", - "2015-06", - "2015-07", - "2015-08", - "2015-09", - "2015-10", - "2015-11", - "2015-12", - "2016-01", - "2016-02", - "2016-03", - "2016-04", - "2016-05", - "2016-06", - "2016-07", - "2016-08", - "2016-09", - "2016-10", - "2016-11", - "2016-12", - "2017-01", - "2017-02", - "2017-03", - "2017-04", - "2017-05", - "2017-06", - "2017-07", - "2017-08", - "2017-09", - "2017-10", - "2017-11", - "2017-12", - "2018-01", - "2018-02", - "2018-03", - "2018-04", - "2018-05", - "2018-06", - "2018-07", - "2018-08", - "2018-09", - "2018-10", - "2018-11", - "2018-12", - "2019-01", - "2019-02", - "2019-03", - "2019-04", - "2019-05", - "2019-06", - "2019-07", - "2019-08", - "2019-09", - "2019-10", - "2019-11", - "2019-12", - "2020-01", - "2020-02", - "2020-03", - "2020-04", - "2020-05", - "2020-06", - "2020-07", - "2020-08", - "2020-09", - "2020-10", - "2020-11", - "2020-12", - "2021-01", - "2021-02", - "2021-03", - "2021-04", - "2021-05", - "2021-06", - "2021-07", - "2021-08", - "2021-09", - "2021-10", - "2021-11", - "2021-12", - "2022-01", - "2022-02", - "2022-03", - "2022-04", - "2022-05", - "2022-06", - "2022-07", - "2022-08", - "2022-09", - "2022-10", - "2022-11", - "2022-12", - "2023-01", - "2023-02", - "2023-03", - "2023-04", - "2023-05", - "2023-06", - "2023-07", - "2023-08", - "2023-09", - "2023-10", - "2023-11", - "2023-12", - "2024-01", - "2024-02", - "2024-03", - "2024-04", - "2024-05", - "2024-06", - "2024-07", - "2024-08", - "2024-09", - "2024-10", - "2024-11", - "2024-12", - ], - "estimated_PTA": [ - -630.0692402, - -620.7267157, - -611.3841912, - -602.0416667, - -592.6991422, - -583.3566176, - -574.0140931, - -564.6715686, - -555.3290441, - -545.9865196, - -536.6439951, - -527.3014706, - -517.9589461, - -508.6164216, - -499.2738971, - -489.9313725, - -480.588848, - -471.2463235, - -461.903799, - -452.5612745, - -443.21875, - -433.8762255, - -424.533701, - -415.1911765, - -405.848652, - -396.5061275, - -387.1636029, - -377.8210784, - -368.4785539, - -359.1360294, - -349.7935049, - -340.4509804, - -331.1084559, - -321.7659314, - -312.4234069, - -303.0808824, - -293.7383578, - -284.3958333, - -275.0533088, - -265.7107843, - -256.3682598, - -247.0257353, - -237.6832108, - -228.3406863, - -218.9981618, - -209.6556373, - -200.3131127, - -190.9705882, - -181.6280637, - -172.2855392, - -162.9430147, - -153.6004902, - -144.2579657, - -134.9154412, - -125.5729167, - -116.2303922, - -106.8878676, - -97.54534314, - -88.20281863, - -78.86029412, - -69.51776961, - -60.1752451, - -50.83272059, - -41.49019608, - -32.14767157, - -22.80514706, - -13.46262255, - -4.120098039, - 5.222426471, - 14.56495098, - 23.90747549, - 33.25, - 42.59252451, - 51.93504902, - 61.27757353, - 70.62009804, - 79.96262255, - 89.30514706, - 98.64767157, - 107.9901961, - 117.3327206, - 126.6752451, - 136.0177696, - 145.3602941, - 154.7028186, - 164.0453431, - 173.3878676, - 182.7303922, - 192.0729167, - 201.4154412, - 210.7579657, - 220.1004902, - 229.4430147, - 238.7855392, - 248.1280637, - 257.4705882, - 266.8131127, - 276.1556373, - 285.4981618, - 294.8406863, - 304.1832108, - 313.5257353, - 322.8682598, - 332.2107843, - 341.5533088, - 350.8958333, - 360.2383578, - 369.5808824, - 378.9234069, - 388.2659314, - 397.6084559, - 406.9509804, - 416.2935049, - 425.6360294, - 434.9785539, - 444.3210784, - 453.6636029, - 463.0061275, - 472.348652, - 481.6911765, - 491.033701, - 500.3762255, - 509.71875, - 519.0612745, - 528.403799, - 537.7463235, - 547.088848, - 556.4313725, - 565.7738971, - 575.1164216, - 584.4589461, - 593.8014706, - 603.1439951, - 612.4865196, - 621.8290441, - 631.1715686, - 640.5140931, - 649.8566176, - 659.1991422, - 668.5416667, - 677.8841912, - 687.2267157, - 696.5692402, - 705.9117647, - 715.2542892, - 724.5968137, - 733.9393382, - 743.2818627, - 752.6243873, - 761.9669118, - 771.3094363, - 780.6519608, - 789.9944853, - 799.3370098, - 808.6795343, - 818.0220588, - 827.3645833, - 836.7071078, - 846.0496324, - 855.3921569, - 864.7346814, - 874.0772059, - 883.4197304, - 892.7622549, - 902.1047794, - 911.4473039, - 920.7898284, - 930.1323529, - 939.4748775, - 948.817402, - 958.1599265, - 967.502451, - 976.8449755, - 986.1875, - 995.5300245, - 1004.872549, - 1014.215074, - 1023.557598, - 1032.900123, - 1042.242647, - 1051.585172, - 1060.927696, - 1070.270221, - 1079.612745, - 1088.95527, - 1098.297794, - 1107.640319, - 1116.982843, - 1126.325368, - 1135.667892, - 1145.010417, - 1154.352941, - 1163.695466, - 1173.03799, - 1182.380515, - 1191.723039, - 1201.065564, - 1210.408088, - 1219.750613, - 1229.093137, - 1238.435662, - 1247.778186, - 1257.120711, - 1266.463235, - 1275.80576, - 1285.148284, - 1294.490809, - 1303.833333, - 1313.175858, - 1322.518382, - 1331.860907, - 1341.203431, - 1350.545956, - 1359.88848, - 1369.231005, - 1378.573529, - 1387.916054, - 1397.258578, - 1406.601103, - 1415.943627, - 1425.286152, - 1434.628676, - 1443.971201, - 1453.313725, - 1462.65625, - 1471.998775, - 1481.341299, - 1490.683824, - ], - }, - ], - ) - AnimalGenetics.initialize_class_variables() - - -def test_initialize_class_variables(mocker: MockerFixture) -> None: - im = InputManager() - mock_im_init = mocker.patch("RUFAS.input_manager.InputManager.__init__", return_value=None) - mock_im_get_data = mocker.patch.object( - im, - "get_data", - side_effect=[ - {"year_month": ["2006-01", "2006-12"], "average": [-1000.0, -900.0], "std": [100.0, 110.0]}, - {"year_month": ["2007-03", "2008-01"], "estimated_PTA": [-620.0, -610.0]}, - ], - ) - - mock_net_merit_base_change = mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.net_merit_base_change", - wraps=AnimalGenetics.net_merit_base_change, - ) - mock_net_merit_fill_gap = mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.net_merit_fill_gap", - wraps=AnimalGenetics.net_merit_fill_gap, - ) - - AnimalGenetics.initialize_class_variables() - - im_get_data_call_list = [call("animal_net_merit"), call("animal_top_listing_semen")] - mock_im_init.assert_called_once() - mock_im_get_data.assert_has_calls(im_get_data_call_list) - - mock_net_merit_base_change.assert_called_once() - mock_net_merit_fill_gap.assert_called_once() - - assert AnimalGenetics.year_month_of_first_net_merit_value == "2006-01" - assert AnimalGenetics.year_month_of_last_net_merit_value == "2007-01" - assert AnimalGenetics.year_month_of_first_top_semen_value == "2007-03" - assert AnimalGenetics.year_month_of_last_top_semen_value == "2008-01" +@pytest.fixture +def genetics() -> Genetics: + AnimalConfig.average_phenotype["fat_kg"] = {2020: 10.0} + AnimalConfig.average_phenotype["protein_kg"] = {2020: 20.0} + genetics = Genetics(animal_type=AnimalType.LAC_COW, birth_year=2020, parity=3) + return genetics @pytest.mark.parametrize( - "original_net_merit, expected_net_merit", + "animal_type, birth_year, birth_month, parity, initialize_new_born_calf, dam_tbv_fat, dam_tbv_protein", [ - ( - { - "2006-02": {"average": 57.974596, "std": 190.5395008512324}, - "2006-05": {"average": 64.173146, "std": 188.6115217330656}, - "2006-08": {"average": 84.630935, "std": 178.08442504055702}, - "2006-11": {"average": 86.763831, "std": 182.9728721565124}, - "2007-02": {"average": 94.058362, "std": 186.64063286936465}, - "2007-05": {"average": 104.253542, "std": 184.97248472260475}, - "2007-08": {"average": 96.521285, "std": 178.3898130245917}, - "2008-01": {"average": 108.275404, "std": 176.0315512646434}, - "2008-04": {"average": 108.139418, "std": 175.72124232039016}, - "2008-08": {"average": 125.954896, "std": 182.93450706094015}, - "2009-01": {"average": 137.945008, "std": 180.76218649894656}, - "2009-04": {"average": 142.742302, "std": 181.46487208201145}, - "2009-08": {"average": 149.801918, "std": 179.51665832875034}, - "2010-01": {"average": 32.080236, "std": 199.78307254666075}, - "2010-04": {"average": 37.527293, "std": 194.6824683454886}, - "2010-08": {"average": 46.648118, "std": 194.84564715450557}, - "2010-12": {"average": 53.252398, "std": 196.859138251821}, - "2011-04": {"average": 68.132048, "std": 182.81341524988173}, - "2011-08": {"average": 76.054593, "std": 186.1919756665264}, - "2011-12": {"average": 84.693403, "std": 186.2432723114572}, - "2012-04": {"average": 99.988792, "std": 190.9506317360085}, - "2012-08": {"average": 108.02154, "std": 190.09990252503655}, - "2012-12": {"average": 112.042317, "std": 189.58854691745364}, - "2013-04": {"average": 132.521979, "std": 189.97862871103044}, - "2013-08": {"average": 142.434448, "std": 190.1762958176841}, - "2013-12": {"average": 153.371769, "std": 191.74361035197663}, - "2014-04": {"average": 174.779201, "std": 187.49870440299472}, - "2014-08": {"average": 186.797579, "std": 189.82049792037407}, - "2014-12": {"average": 9.079418, "std": 195.16527666770352}, - "2015-04": {"average": 29.411005, "std": 197.64056136808043}, - "2015-08": {"average": 43.5462, "std": 201.43483395272037}, - "2015-12": {"average": 53.902095, "std": 200.7894938377279}, - "2016-04": {"average": 74.315331, "std": 205.435898623781}, - "2016-08": {"average": 81.337129, "std": 208.40773477737665}, - "2016-12": {"average": 98.030341, "std": 211.27855662708345}, - "2017-04": {"average": 120.137081, "std": 206.65197545099693}, - "2017-08": {"average": 133.776755, "std": 210.5052914505237}, - "2017-12": {"average": 159.840968, "std": 218.48832843615}, - "2018-04": {"average": 165.349842, "std": 213.01573968271688}, - "2018-08": {"average": 186.885319, "std": 225.43930020133632}, - "2018-12": {"average": 203.958502, "std": 225.3560229989782}, - "2019-04": {"average": 217.449203, "std": 227.96290467456495}, - "2019-08": {"average": 232.664942, "std": 226.14003185667204}, - "2019-12": {"average": 250.213756, "std": 229.6848838046868}, - "2020-04": {"average": 36.931108, "std": 239.4382103255292}, - "2020-08": {"average": 51.71824, "std": 233.29549959933297}, - "2020-12": {"average": 67.14513, "std": 238.4551655034612}, - "2021-04": {"average": 91.328729, "std": 245.5784142636412}, - "2021-08": {"average": 130.637185, "std": 306.1845619594099}, - "2021-12": {"average": 144.61792, "std": 313.8841273987482}, - "2022-04": {"average": 170.722282, "std": 323.88013733279865}, - "2022-08": {"average": 198.053415, "std": 332.7844850437559}, - "2022-12": {"average": 218.543242, "std": 333.9914991195576}, - "2023-04": {"average": 249.42981, "std": 338.80938128594363}, - "2023-08": {"average": 273.408704, "std": 340.6332091605873}, - "2023-12": {"average": 296.231263, "std": 345.74939244259684}, - "2024-04": {"average": 322.6829, "std": 353.4989587814793}, - "2024-08": {"average": 360.731239, "std": 364.4961663811087}, - }, - { - "2006-02": {"average": -489.025404, "std": 190.5395008512324}, - "2006-05": {"average": -482.826854, "std": 188.6115217330656}, - "2006-08": {"average": -462.369065, "std": 178.08442504055702}, - "2006-11": {"average": -460.236169, "std": 182.9728721565124}, - "2007-02": {"average": -452.941638, "std": 186.64063286936465}, - "2007-05": {"average": -442.746458, "std": 184.97248472260475}, - "2007-08": {"average": -450.47871499999997, "std": 178.3898130245917}, - "2008-01": {"average": -438.724596, "std": 176.0315512646434}, - "2008-04": {"average": -438.860582, "std": 175.72124232039016}, - "2008-08": {"average": -421.045104, "std": 182.93450706094015}, - "2009-01": {"average": -409.05499199999997, "std": 180.76218649894656}, - "2009-04": {"average": -404.257698, "std": 181.46487208201145}, - "2009-08": {"average": -397.198082, "std": 179.51665832875034}, - "2010-01": {"average": -382.919764, "std": 199.78307254666075}, - "2010-04": {"average": -377.472707, "std": 194.6824683454886}, - "2010-08": {"average": -368.351882, "std": 194.84564715450557}, - "2010-12": {"average": -361.74760200000003, "std": 196.859138251821}, - "2011-04": {"average": -346.867952, "std": 182.81341524988173}, - "2011-08": {"average": -338.945407, "std": 186.1919756665264}, - "2011-12": {"average": -330.306597, "std": 186.2432723114572}, - "2012-04": {"average": -315.011208, "std": 190.9506317360085}, - "2012-08": {"average": -306.97846, "std": 190.09990252503655}, - "2012-12": {"average": -302.957683, "std": 189.58854691745364}, - "2013-04": {"average": -282.478021, "std": 189.97862871103044}, - "2013-08": {"average": -272.565552, "std": 190.1762958176841}, - "2013-12": {"average": -261.628231, "std": 191.74361035197663}, - "2014-04": {"average": -240.220799, "std": 187.49870440299472}, - "2014-08": {"average": -228.20242099999996, "std": 189.82049792037407}, - "2014-12": {"average": -221.92058200000002, "std": 195.16527666770352}, - "2015-04": {"average": -201.588995, "std": 197.64056136808043}, - "2015-08": {"average": -187.4538, "std": 201.43483395272037}, - "2015-12": {"average": -177.09790499999997, "std": 200.7894938377279}, - "2016-04": {"average": -156.68466899999999, "std": 205.435898623781}, - "2016-08": {"average": -149.662871, "std": 208.40773477737665}, - "2016-12": {"average": -132.96965899999998, "std": 211.27855662708345}, - "2017-04": {"average": -110.86291900000003, "std": 206.65197545099693}, - "2017-08": {"average": -97.22324500000002, "std": 210.5052914505237}, - "2017-12": {"average": -71.15903200000002, "std": 218.48832843615}, - "2018-04": {"average": -65.65015800000003, "std": 213.01573968271688}, - "2018-08": {"average": -44.11468100000002, "std": 225.43930020133632}, - "2018-12": {"average": -27.041498000000047, "std": 225.3560229989782}, - "2019-04": {"average": -13.550796999999989, "std": 227.96290467456495}, - "2019-08": {"average": 1.6649419999999964, "std": 226.14003185667204}, - "2019-12": {"average": 19.21375599999999, "std": 229.6848838046868}, - "2020-04": {"average": 36.931107999999995, "std": 239.4382103255292}, - "2020-08": {"average": 51.71824000000004, "std": 233.29549959933297}, - "2020-12": {"average": 67.14513, "std": 238.4551655034612}, - "2021-04": {"average": 91.32872899999995, "std": 245.5784142636412}, - "2021-08": {"average": 130.63718500000004, "std": 306.1845619594099}, - "2021-12": {"average": 144.61792000000003, "std": 313.8841273987482}, - "2022-04": {"average": 170.72228199999995, "std": 323.88013733279865}, - "2022-08": {"average": 198.05341499999997, "std": 332.7844850437559}, - "2022-12": {"average": 218.54324199999996, "std": 333.9914991195576}, - "2023-04": {"average": 249.42980999999997, "std": 338.80938128594363}, - "2023-08": {"average": 273.40870399999994, "std": 340.6332091605873}, - "2023-12": {"average": 296.231263, "std": 345.74939244259684}, - "2024-04": {"average": 322.6829, "std": 353.4989587814793}, - "2024-08": {"average": 360.73123899999996, "std": 364.4961663811087}, - }, - ) + (AnimalType.CALF, 2020, None, False, None, None, None), + (AnimalType.HEIFER_I, 2020, None, None, None, None, None), + (AnimalType.HEIFER_II, 2020, None, None, None, None, None), + (AnimalType.HEIFER_III, 2020, None, 0, None, None, None), + (AnimalType.LAC_COW, 2020, None, 1, None, None, None), + (AnimalType.DRY_COW, 2020, None, 5, None, None, None), + (AnimalType.CALF, 2020, 1, 1, True, 10.0, 20.0), ], ) -def test_net_merit_base_change( - original_net_merit: dict[str, dict[str, float]], expected_net_merit: dict[str, dict[str, float]] +def test_animal_genetics_init( + animal_type: AnimalType, + birth_year: int, + birth_month: int | None, + parity: int | None, + initialize_new_born_calf: bool | None, + dam_tbv_fat: float | None, + dam_tbv_protein: float | None, + mocker: MockerFixture, ) -> None: - result = AnimalGenetics.net_merit_base_change({"HO": original_net_merit}) - - assert result == {"HO": expected_net_merit} + """Unit test for __init__()""" + mock_calculate_newborn_calf_tbv_values = mocker.patch( + "RUFAS.biophysical.animal.animal_genetics.animal_genetics.Genetics._calculate_newborn_calf_tbv_values", + return_value=(10.0, 20.0), + ) + mock_calculate_tbv_values = mocker.patch( + "RUFAS.biophysical.animal.animal_genetics.animal_genetics.Genetics._calculate_tbv_values", + return_value=(10.0, 20.0), + ) + mock_calculate_ep_values = mocker.patch( + "RUFAS.biophysical.animal.animal_genetics.animal_genetics.Genetics._calculate_ep_values", + return_value=(10.0, 20.0), + ) + mock_calculate_et_values = mocker.patch( + "RUFAS.biophysical.animal.animal_genetics.animal_genetics.Genetics._calculate_et_values", + return_value=(10.0, 20.0), + ) + mock_calculate_phenotype_values = mocker.patch( + "RUFAS.biophysical.animal.animal_genetics.animal_genetics.Genetics._calculate_phenotype_values", + return_value=(10.0, 20.0), + ) + mock_calculate_ebv_values = mocker.patch( + "RUFAS.biophysical.animal.animal_genetics.animal_genetics.Genetics._calculate_ebv_values", + return_value=(10.0, 20.0), + ) + mock_calculate_ranking_index = mocker.patch( + "RUFAS.biophysical.animal.animal_genetics.animal_genetics.Genetics._calculate_ranking_index", + return_value=10.0, + ) + Genetics( + animal_type=animal_type, + birth_year=birth_year, + birth_month=birth_month, + parity=parity, + initialize_new_born_calf=initialize_new_born_calf, + dam_tbv_fat=dam_tbv_fat, + dam_tbv_protein=dam_tbv_protein, + ) -@pytest.mark.parametrize( - "original_net_merit, expected_net_merit", - [ - ( - { - "2006-02": {"average": -704.025404, "std": 190.5395008512324}, - "2006-05": {"average": -697.826854, "std": 188.6115217330656}, - "2006-08": {"average": -677.369065, "std": 178.08442504055702}, - "2006-11": {"average": -675.236169, "std": 182.9728721565124}, - "2007-02": {"average": -667.941638, "std": 186.64063286936465}, - "2007-05": {"average": -657.7464580000001, "std": 184.97248472260475}, - "2007-08": {"average": -665.478715, "std": 178.3898130245917}, - "2008-01": {"average": -653.724596, "std": 176.0315512646434}, - "2008-04": {"average": -653.860582, "std": 175.72124232039016}, - "2008-08": {"average": -636.045104, "std": 182.93450706094015}, - "2009-01": {"average": -624.054992, "std": 180.76218649894656}, - "2009-04": {"average": -619.257698, "std": 181.46487208201145}, - "2009-08": {"average": -612.198082, "std": 179.51665832875034}, - "2010-01": {"average": -589.919764, "std": 199.78307254666075}, - "2010-04": {"average": -584.472707, "std": 194.6824683454886}, - "2010-08": {"average": -575.3518819999999, "std": 194.84564715450557}, - "2010-12": {"average": -568.747602, "std": 196.859138251821}, - "2011-04": {"average": -553.8679520000001, "std": 182.81341524988173}, - "2011-08": {"average": -545.9454069999999, "std": 186.1919756665264}, - "2011-12": {"average": -537.306597, "std": 186.2432723114572}, - "2012-04": {"average": -522.011208, "std": 190.9506317360085}, - "2012-08": {"average": -513.97846, "std": 190.09990252503655}, - "2012-12": {"average": -509.957683, "std": 189.58854691745364}, - "2013-04": {"average": -489.478021, "std": 189.97862871103044}, - "2013-08": {"average": -479.565552, "std": 190.1762958176841}, - "2013-12": {"average": -468.628231, "std": 191.74361035197663}, - "2014-04": {"average": -447.220799, "std": 187.49870440299472}, - "2014-08": {"average": -435.20242099999996, "std": 189.82049792037407}, - "2014-12": {"average": -359.920582, "std": 195.16527666770352}, - "2015-04": {"average": -339.58899499999995, "std": 197.64056136808043}, - "2015-08": {"average": -325.4538, "std": 201.43483395272037}, - "2015-12": {"average": -315.09790499999997, "std": 200.7894938377279}, - "2016-04": {"average": -294.684669, "std": 205.435898623781}, - "2016-08": {"average": -287.662871, "std": 208.40773477737665}, - "2016-12": {"average": -270.969659, "std": 211.27855662708345}, - "2017-04": {"average": -248.86291900000003, "std": 206.65197545099693}, - "2017-08": {"average": -235.22324500000002, "std": 210.5052914505237}, - "2017-12": {"average": -209.15903200000002, "std": 218.48832843615}, - "2018-04": {"average": -203.65015800000003, "std": 213.01573968271688}, - "2018-08": {"average": -182.11468100000002, "std": 225.43930020133632}, - "2018-12": {"average": -165.04149800000005, "std": 225.3560229989782}, - "2019-04": {"average": -151.550797, "std": 227.96290467456495}, - "2019-08": {"average": -136.335058, "std": 226.14003185667204}, - "2019-12": {"average": -118.78624400000001, "std": 229.6848838046868}, - "2020-04": {"average": 36.931107999999995, "std": 239.4382103255292}, - "2020-08": {"average": 51.71824000000004, "std": 233.29549959933297}, - "2020-12": {"average": 67.14513, "std": 238.4551655034612}, - "2021-04": {"average": 91.32872899999995, "std": 245.5784142636412}, - "2021-08": {"average": 130.63718500000004, "std": 306.1845619594099}, - "2021-12": {"average": 144.61792000000003, "std": 313.8841273987482}, - "2022-04": {"average": 170.72228199999995, "std": 323.88013733279865}, - "2022-08": {"average": 198.0534150000001, "std": 332.7844850437559}, - "2022-12": {"average": 218.54324199999996, "std": 333.9914991195576}, - "2023-04": {"average": 249.4298100000001, "std": 338.80938128594363}, - "2023-08": {"average": 273.40870399999994, "std": 340.6332091605873}, - "2023-12": {"average": 296.2312630000001, "std": 345.74939244259684}, - "2024-04": {"average": 322.6829, "std": 353.4989587814793}, - "2024-08": {"average": 360.73123899999996, "std": 364.4961663811087}, - }, - { - "2006-02": {"average": -704.025404, "std": 190.5395008512324}, - "2006-03": {"average": -701.8254039999999, "std": 190.5395008512324}, - "2006-04": {"average": -699.625404, "std": 190.5395008512324}, - "2006-05": {"average": -697.826854, "std": 188.6115217330656}, - "2006-06": {"average": -695.626854, "std": 188.6115217330656}, - "2006-07": {"average": -693.426854, "std": 188.6115217330656}, - "2006-08": {"average": -677.369065, "std": 178.08442504055702}, - "2006-09": {"average": -675.1690649999999, "std": 178.08442504055702}, - "2006-10": {"average": -672.969065, "std": 178.08442504055702}, - "2006-11": {"average": -675.236169, "std": 182.9728721565124}, - "2006-12": {"average": -673.036169, "std": 182.9728721565124}, - "2007-01": {"average": -670.836169, "std": 182.9728721565124}, - "2007-02": {"average": -667.941638, "std": 186.64063286936465}, - "2007-03": {"average": -665.741638, "std": 186.64063286936465}, - "2007-04": {"average": -663.541638, "std": 186.64063286936465}, - "2007-05": {"average": -657.7464580000001, "std": 184.97248472260475}, - "2007-06": {"average": -655.546458, "std": 184.97248472260475}, - "2007-07": {"average": -653.3464580000001, "std": 184.97248472260475}, - "2007-08": {"average": -665.478715, "std": 178.3898130245917}, - "2007-09": {"average": -663.2787149999999, "std": 178.3898130245917}, - "2007-10": {"average": -661.078715, "std": 178.3898130245917}, - "2007-11": {"average": -658.8787149999999, "std": 178.3898130245917}, - "2007-12": {"average": -656.678715, "std": 178.3898130245917}, - "2008-01": {"average": -653.724596, "std": 176.0315512646434}, - "2008-02": {"average": -651.524596, "std": 176.0315512646434}, - "2008-03": {"average": -649.324596, "std": 176.0315512646434}, - "2008-04": {"average": -653.860582, "std": 175.72124232039016}, - "2008-05": {"average": -651.660582, "std": 175.72124232039016}, - "2008-06": {"average": -649.460582, "std": 175.72124232039016}, - "2008-07": {"average": -647.260582, "std": 175.72124232039016}, - "2008-08": {"average": -636.045104, "std": 182.93450706094015}, - "2008-09": {"average": -633.845104, "std": 182.93450706094015}, - "2008-10": {"average": -631.6451040000001, "std": 182.93450706094015}, - "2008-11": {"average": -629.445104, "std": 182.93450706094015}, - "2008-12": {"average": -627.2451040000001, "std": 182.93450706094015}, - "2009-01": {"average": -624.054992, "std": 180.76218649894656}, - "2009-02": {"average": -621.8549919999999, "std": 180.76218649894656}, - "2009-03": {"average": -619.654992, "std": 180.76218649894656}, - "2009-04": {"average": -619.257698, "std": 181.46487208201145}, - "2009-05": {"average": -617.057698, "std": 181.46487208201145}, - "2009-06": {"average": -614.857698, "std": 181.46487208201145}, - "2009-07": {"average": -612.657698, "std": 181.46487208201145}, - "2009-08": {"average": -612.198082, "std": 179.51665832875034}, - "2009-09": {"average": -609.998082, "std": 179.51665832875034}, - "2009-10": {"average": -607.798082, "std": 179.51665832875034}, - "2009-11": {"average": -605.598082, "std": 179.51665832875034}, - "2009-12": {"average": -603.398082, "std": 179.51665832875034}, - "2010-01": {"average": -589.919764, "std": 199.78307254666075}, - "2010-02": {"average": -586.8530973333333, "std": 199.78307254666075}, - "2010-03": {"average": -583.7864306666667, "std": 199.78307254666075}, - "2010-04": {"average": -584.472707, "std": 194.6824683454886}, - "2010-05": {"average": -581.4060403333333, "std": 194.6824683454886}, - "2010-06": {"average": -578.3393736666667, "std": 194.6824683454886}, - "2010-07": {"average": -575.272707, "std": 194.6824683454886}, - "2010-08": {"average": -575.3518819999999, "std": 194.84564715450557}, - "2010-09": {"average": -572.2852153333332, "std": 194.84564715450557}, - "2010-10": {"average": -569.2185486666666, "std": 194.84564715450557}, - "2010-11": {"average": -566.1518819999999, "std": 194.84564715450557}, - "2010-12": {"average": -568.747602, "std": 196.859138251821}, - "2011-01": {"average": -565.6809353333333, "std": 196.859138251821}, - "2011-02": {"average": -562.6142686666667, "std": 196.859138251821}, - "2011-03": {"average": -559.547602, "std": 196.859138251821}, - "2011-04": {"average": -553.8679520000001, "std": 182.81341524988173}, - "2011-05": {"average": -550.8012853333333, "std": 182.81341524988173}, - "2011-06": {"average": -547.7346186666667, "std": 182.81341524988173}, - "2011-07": {"average": -544.667952, "std": 182.81341524988173}, - "2011-08": {"average": -545.9454069999999, "std": 186.1919756665264}, - "2011-09": {"average": -542.8787403333332, "std": 186.1919756665264}, - "2011-10": {"average": -539.8120736666666, "std": 186.1919756665264}, - "2011-11": {"average": -536.7454069999999, "std": 186.1919756665264}, - "2011-12": {"average": -537.306597, "std": 186.2432723114572}, - "2012-01": {"average": -534.2399303333333, "std": 186.2432723114572}, - "2012-02": {"average": -531.1732636666667, "std": 186.2432723114572}, - "2012-03": {"average": -528.106597, "std": 186.2432723114572}, - "2012-04": {"average": -522.011208, "std": 190.9506317360085}, - "2012-05": {"average": -518.9445413333333, "std": 190.9506317360085}, - "2012-06": {"average": -515.8778746666667, "std": 190.9506317360085}, - "2012-07": {"average": -512.811208, "std": 190.9506317360085}, - "2012-08": {"average": -513.97846, "std": 190.09990252503655}, - "2012-09": {"average": -510.9117933333334, "std": 190.09990252503655}, - "2012-10": {"average": -507.8451266666667, "std": 190.09990252503655}, - "2012-11": {"average": -504.77846000000005, "std": 190.09990252503655}, - "2012-12": {"average": -509.957683, "std": 189.58854691745364}, - "2013-01": {"average": -506.8910163333333, "std": 189.58854691745364}, - "2013-02": {"average": -503.82434966666665, "std": 189.58854691745364}, - "2013-03": {"average": -500.757683, "std": 189.58854691745364}, - "2013-04": {"average": -489.478021, "std": 189.97862871103044}, - "2013-05": {"average": -486.41135433333335, "std": 189.97862871103044}, - "2013-06": {"average": -483.3446876666667, "std": 189.97862871103044}, - "2013-07": {"average": -480.278021, "std": 189.97862871103044}, - "2013-08": {"average": -479.565552, "std": 190.1762958176841}, - "2013-09": {"average": -476.49888533333336, "std": 190.1762958176841}, - "2013-10": {"average": -473.4322186666667, "std": 190.1762958176841}, - "2013-11": {"average": -470.36555200000004, "std": 190.1762958176841}, - "2013-12": {"average": -468.628231, "std": 191.74361035197663}, - "2014-01": {"average": -465.56156433333337, "std": 191.74361035197663}, - "2014-02": {"average": -462.4948976666667, "std": 191.74361035197663}, - "2014-03": {"average": -459.42823100000004, "std": 191.74361035197663}, - "2014-04": {"average": -447.220799, "std": 187.49870440299472}, - "2014-05": {"average": -444.15413233333334, "std": 187.49870440299472}, - "2014-06": {"average": -441.0874656666667, "std": 187.49870440299472}, - "2014-07": {"average": -438.020799, "std": 187.49870440299472}, - "2014-08": {"average": -435.20242099999996, "std": 189.82049792037407}, - "2014-09": {"average": -432.1357543333333, "std": 189.82049792037407}, - "2014-10": {"average": -429.06908766666663, "std": 189.82049792037407}, - "2014-11": {"average": -426.00242099999997, "std": 189.82049792037407}, - "2014-12": {"average": -359.920582, "std": 195.16527666770352}, - "2015-01": {"average": -356.070582, "std": 195.16527666770352}, - "2015-02": {"average": -352.22058200000004, "std": 195.16527666770352}, - "2015-03": {"average": -348.370582, "std": 195.16527666770352}, - "2015-04": {"average": -339.58899499999995, "std": 197.64056136808043}, - "2015-05": {"average": -335.73899499999993, "std": 197.64056136808043}, - "2015-06": {"average": -331.88899499999997, "std": 197.64056136808043}, - "2015-07": {"average": -328.03899499999994, "std": 197.64056136808043}, - "2015-08": {"average": -325.4538, "std": 201.43483395272037}, - "2015-09": {"average": -321.6038, "std": 201.43483395272037}, - "2015-10": {"average": -317.7538, "std": 201.43483395272037}, - "2015-11": {"average": -313.9038, "std": 201.43483395272037}, - "2015-12": {"average": -315.09790499999997, "std": 200.7894938377279}, - "2016-01": {"average": -311.24790499999995, "std": 200.7894938377279}, - "2016-02": {"average": -307.397905, "std": 200.7894938377279}, - "2016-03": {"average": -303.54790499999996, "std": 200.7894938377279}, - "2016-04": {"average": -294.684669, "std": 205.435898623781}, - "2016-05": {"average": -290.83466899999996, "std": 205.435898623781}, - "2016-06": {"average": -286.984669, "std": 205.435898623781}, - "2016-07": {"average": -283.134669, "std": 205.435898623781}, - "2016-08": {"average": -287.662871, "std": 208.40773477737665}, - "2016-09": {"average": -283.812871, "std": 208.40773477737665}, - "2016-10": {"average": -279.962871, "std": 208.40773477737665}, - "2016-11": {"average": -276.112871, "std": 208.40773477737665}, - "2016-12": {"average": -270.969659, "std": 211.27855662708345}, - "2017-01": {"average": -267.11965899999996, "std": 211.27855662708345}, - "2017-02": {"average": -263.269659, "std": 211.27855662708345}, - "2017-03": {"average": -259.41965899999997, "std": 211.27855662708345}, - "2017-04": {"average": -248.86291900000003, "std": 206.65197545099693}, - "2017-05": {"average": -245.01291900000004, "std": 206.65197545099693}, - "2017-06": {"average": -241.16291900000004, "std": 206.65197545099693}, - "2017-07": {"average": -237.31291900000002, "std": 206.65197545099693}, - "2017-08": {"average": -235.22324500000002, "std": 210.5052914505237}, - "2017-09": {"average": -231.37324500000003, "std": 210.5052914505237}, - "2017-10": {"average": -227.52324500000003, "std": 210.5052914505237}, - "2017-11": {"average": -223.673245, "std": 210.5052914505237}, - "2017-12": {"average": -209.15903200000002, "std": 218.48832843615}, - "2018-01": {"average": -205.30903200000003, "std": 218.48832843615}, - "2018-02": {"average": -201.45903200000004, "std": 218.48832843615}, - "2018-03": {"average": -197.609032, "std": 218.48832843615}, - "2018-04": {"average": -203.65015800000003, "std": 213.01573968271688}, - "2018-05": {"average": -199.80015800000004, "std": 213.01573968271688}, - "2018-06": {"average": -195.95015800000004, "std": 213.01573968271688}, - "2018-07": {"average": -192.10015800000002, "std": 213.01573968271688}, - "2018-08": {"average": -182.11468100000002, "std": 225.43930020133632}, - "2018-09": {"average": -178.26468100000002, "std": 225.43930020133632}, - "2018-10": {"average": -174.41468100000003, "std": 225.43930020133632}, - "2018-11": {"average": -170.564681, "std": 225.43930020133632}, - "2018-12": {"average": -165.04149800000005, "std": 225.3560229989782}, - "2019-01": {"average": -161.19149800000005, "std": 225.3560229989782}, - "2019-02": {"average": -157.34149800000006, "std": 225.3560229989782}, - "2019-03": {"average": -153.49149800000004, "std": 225.3560229989782}, - "2019-04": {"average": -151.550797, "std": 227.96290467456495}, - "2019-05": {"average": -147.700797, "std": 227.96290467456495}, - "2019-06": {"average": -143.850797, "std": 227.96290467456495}, - "2019-07": {"average": -140.00079699999998, "std": 227.96290467456495}, - "2019-08": {"average": -136.335058, "std": 226.14003185667204}, - "2019-09": {"average": -132.485058, "std": 226.14003185667204}, - "2019-10": {"average": -128.63505800000001, "std": 226.14003185667204}, - "2019-11": {"average": -124.785058, "std": 226.14003185667204}, - "2019-12": {"average": -118.78624400000001, "std": 229.6848838046868}, - "2020-01": {"average": -112.0404079375, "std": 229.6848838046868}, - "2020-02": {"average": -105.294571875, "std": 229.6848838046868}, - "2020-03": {"average": -98.54873581250001, "std": 229.6848838046868}, - "2020-04": {"average": 36.931107999999995, "std": 239.4382103255292}, - "2020-05": {"average": 43.6769440625, "std": 239.4382103255292}, - "2020-06": {"average": 50.422780124999996, "std": 239.4382103255292}, - "2020-07": {"average": 57.16861618749999, "std": 239.4382103255292}, - "2020-08": {"average": 51.71824000000004, "std": 233.29549959933297}, - "2020-09": {"average": 58.46407606250004, "std": 233.29549959933297}, - "2020-10": {"average": 65.20991212500005, "std": 233.29549959933297}, - "2020-11": {"average": 71.95574818750003, "std": 233.29549959933297}, - "2020-12": {"average": 67.14513, "std": 238.4551655034612}, - "2021-01": {"average": 73.8909660625, "std": 238.4551655034612}, - "2021-02": {"average": 80.636802125, "std": 238.4551655034612}, - "2021-03": {"average": 87.38263818749999, "std": 238.4551655034612}, - "2021-04": {"average": 91.32872899999995, "std": 245.5784142636412}, - "2021-05": {"average": 98.07456506249996, "std": 245.5784142636412}, - "2021-06": {"average": 104.82040112499996, "std": 245.5784142636412}, - "2021-07": {"average": 111.56623718749995, "std": 245.5784142636412}, - "2021-08": {"average": 130.63718500000004, "std": 306.1845619594099}, - "2021-09": {"average": 137.38302106250003, "std": 306.1845619594099}, - "2021-10": {"average": 144.12885712500005, "std": 306.1845619594099}, - "2021-11": {"average": 150.87469318750004, "std": 306.1845619594099}, - "2021-12": {"average": 144.61792000000003, "std": 313.8841273987482}, - "2022-01": {"average": 151.36375606250002, "std": 313.8841273987482}, - "2022-02": {"average": 158.10959212500003, "std": 313.8841273987482}, - "2022-03": {"average": 164.85542818750002, "std": 313.8841273987482}, - "2022-04": {"average": 170.72228199999995, "std": 323.88013733279865}, - "2022-05": {"average": 177.46811806249994, "std": 323.88013733279865}, - "2022-06": {"average": 184.21395412499996, "std": 323.88013733279865}, - "2022-07": {"average": 190.95979018749995, "std": 323.88013733279865}, - "2022-08": {"average": 198.0534150000001, "std": 332.7844850437559}, - "2022-09": {"average": 204.79925106250008, "std": 332.7844850437559}, - "2022-10": {"average": 211.5450871250001, "std": 332.7844850437559}, - "2022-11": {"average": 218.29092318750008, "std": 332.7844850437559}, - "2022-12": {"average": 218.54324199999996, "std": 333.9914991195576}, - "2023-01": {"average": 225.28907806249995, "std": 333.9914991195576}, - "2023-02": {"average": 232.03491412499997, "std": 333.9914991195576}, - "2023-03": {"average": 238.78075018749996, "std": 333.9914991195576}, - "2023-04": {"average": 249.4298100000001, "std": 338.80938128594363}, - "2023-05": {"average": 256.1756460625001, "std": 338.80938128594363}, - "2023-06": {"average": 262.92148212500007, "std": 338.80938128594363}, - "2023-07": {"average": 269.6673181875001, "std": 338.80938128594363}, - "2023-08": {"average": 273.40870399999994, "std": 340.6332091605873}, - "2023-09": {"average": 280.15454006249996, "std": 340.6332091605873}, - "2023-10": {"average": 286.9003761249999, "std": 340.6332091605873}, - "2023-11": {"average": 293.64621218749994, "std": 340.6332091605873}, - "2023-12": {"average": 296.2312630000001, "std": 345.74939244259684}, - "2024-01": {"average": 302.97709906250014, "std": 345.74939244259684}, - "2024-02": {"average": 309.7229351250001, "std": 345.74939244259684}, - "2024-03": {"average": 316.4687711875001, "std": 345.74939244259684}, - "2024-04": {"average": 322.6829, "std": 353.4989587814793}, - "2024-05": {"average": 329.42873606250004, "std": 353.4989587814793}, - "2024-06": {"average": 336.174572125, "std": 353.4989587814793}, - "2024-07": {"average": 342.9204081875, "std": 353.4989587814793}, - "2024-08": {"average": 360.73123899999996, "std": 364.4961663811087}, - "2024-09": {"average": 367.4770750625, "std": 364.4961663811087}, - "2024-10": {"average": 374.22291112499994, "std": 364.4961663811087}, - "2024-11": {"average": 380.96874718749996, "std": 364.4961663811087}, - "2024-12": {"average": 387.71458325, "std": 364.4961663811087}, - }, + if initialize_new_born_calf: + mock_calculate_newborn_calf_tbv_values.assert_called_once_with( + dam_tbv_fat, dam_tbv_protein, f"{birth_year}-{birth_month:02d}" ) - ], -) -def test_net_merit_fill_gap( - original_net_merit: dict[str, dict[str, float]], expected_net_merit: dict[str, dict[str, float]] -) -> None: - result = AnimalGenetics.net_merit_fill_gap({"HO": original_net_merit}) - - assert result == {"HO": expected_net_merit} + mock_calculate_tbv_values.assert_not_called() + else: + mock_calculate_newborn_calf_tbv_values.assert_not_called() + mock_calculate_tbv_values.assert_called_once_with() + mock_calculate_ep_values.assert_called_once_with() + mock_calculate_et_values.assert_called_once_with() + mock_calculate_phenotype_values.assert_called_once_with(birth_year=birth_year) + mock_calculate_ebv_values.assert_not_called() + mock_calculate_ranking_index.assert_not_called() @pytest.mark.parametrize( - "birth_date, breed, expected_value", + "birth_year, animal_type, parity, ebv_recalculate", [ - ("2006-12-12", Breed.HO, (-458.03616900000003, 182.9728721565124)), - ("2008-08-08", Breed.HO, (-421.045104, 182.93450706094015)), - ("2010-11-05", Breed.HO, (-359.151882, 194.84564715450557)), - ("2012-06-08", Breed.HO, (-308.8778746666667, 190.9506317360085)), - ("2014-01-31", Breed.HO, (-258.56156433333337, 191.74361035197663)), - ("2016-09-06", Breed.HO, (-145.812871, 208.40773477737665)), - ("2018-08-18", Breed.HO, (-44.11468100000002, 225.43930020133632)), - ("2021-03-01", Breed.HO, (87.38263818749999, 238.4551655034612)), - ("2022-10-04", Breed.HO, (211.54508712499998, 332.7844850437559)), - ("2024-06-28", Breed.HO, (336.174572125, 353.4989587814793)), + (2020, AnimalType.LAC_COW, 0, True), + (2020, AnimalType.LAC_COW, 1, True), + (2020, AnimalType.LAC_COW, 2, True), + (2020, AnimalType.LAC_COW, 3, True), + (2020, AnimalType.LAC_COW, 4, False), + (2020, AnimalType.LAC_COW, 5, False), ], ) -def test_assign_net_merit_value_to_animals_entering_herd( - birth_date: str, - breed: Breed, - expected_value: Tuple[float, float], +def test_recalculate_values_at_lactation_start( + birth_year: int, + animal_type: AnimalType, + parity: int, + ebv_recalculate: bool, + genetics: Genetics, mocker: MockerFixture, ) -> None: - mock_generate_random_number = mocker.patch.object( - Utility, "generate_random_number", side_effect=lambda avg, std: (avg, std) + """Unit test for recalculate_values_at_lactation_start()""" + mock_calculate_et_values = mocker.patch.object(genetics, "_calculate_et_values", return_value=(10.0, 20.0)) + mock_calculate_phenotype_values = mocker.patch.object( + genetics, "_calculate_phenotype_values", return_value=(10.0, 20.0) ) - mock_clamp_date = mocker.patch.object( - AnimalGenetics, - "_clamp_birth_year_month_in_data_range", - wraps=AnimalGenetics._clamp_birth_year_month_in_data_range, + mock_calculate_ebv_values = mocker.patch.object(genetics, "_calculate_ebv_values", return_value=(10.0, 20.0)) + mock_calculate_ranking_index = mocker.patch.object(genetics, "_calculate_ranking_index", return_value=10.0) + + genetics.recalculate_values_at_lactation_start( + birth_year=birth_year, + animal_type=animal_type, + parity=parity, + group_specific_TBV_fat_mean=1.1, + group_specific_TBV_protein_mean=2.2, ) - setup_animal_genetics(mocker) - result = AnimalGenetics.assign_net_merit_value_to_animals_entering_herd(birth_date, breed) - assert result == expected_value - mock_generate_random_number.assert_called_once_with(expected_value[0], expected_value[1]) - mock_clamp_date.assert_called_once() + mock_calculate_et_values.assert_called_once_with() + mock_calculate_phenotype_values.assert_called_once_with(birth_year=birth_year) + if ebv_recalculate: + mock_calculate_ebv_values.assert_called_once_with( + animal_type=animal_type, parity=parity, group_specific_TBV_fat_mean=1.1, group_specific_TBV_protein_mean=2.2 + ) + else: + mock_calculate_ebv_values.assert_not_called() + mock_calculate_ranking_index.assert_called_once_with() + + +def test_calculate_tbv_values(genetics: Genetics, mocker: MockerFixture) -> None: + """Unit test for _calculate_tbv_values()""" + mock_generate_bivariate_random_numbers = mocker.patch.object( + Utility, "generate_bivariate_random_numbers", return_value=(10.0, 20.0) + ) + genetics._calculate_tbv_values() + mock_generate_bivariate_random_numbers.assert_called_once_with( + 0.0, 0.0, TBV_FAT_STD, TBV_PROTEIN_STD, TBV_CORRELATION + ) @pytest.mark.parametrize( - "birth_date, breed, dam_net_merit_value", + "dam_tbv_fat, dam_tbv_protein, birth_year_month, top_listing_semen_fat, top_listing_semen_protein," + "expected_mean_tbv_fat, expected_mean_tbv_protein, expected_std_tbv_fat, expected_std_tbv_protein", [ - ("2006-12-12", Breed.HO, -672.9028356666666), - ("2008-08-08", Breed.HO, -636.045104), - ("2010-11-05", Breed.HO, -562.701882), - ("2012-06-08", Breed.HO, -513.5778746666667), - ("2014-01-31", Breed.HO, -464.4115643333334), - ("2016-09-06", Breed.HO, -281.512871), - ("2018-08-18", Breed.HO, -182.11468100000002), - ("2021-03-01", Breed.HO, 84.89513), - ("2022-10-04", Breed.HO, 209.88674833333343), - ("2024-06-28", Breed.HO, 334.51623333333333), + (10.0, 20.0, "2020-01", 13.0, 18.0, 11.5, 19.0, 18.243354954612926, 9.475230867899738), + (15.0, 25.0, "2021-01", 15.0, 25.0, 15.0, 25.0, 18.243354954612926, 9.475230867899738), + (20.0, 30.0, "2022-01", 128.0, 64.0, 74.0, 47.0, 18.243354954612926, 9.475230867899738), ], ) -def test_assign_net_merit_value_to_newborn_calf( - birth_date: str, breed: Breed, dam_net_merit_value: float, mocker: MockerFixture +def test_calculate_newborn_calf_tbv_values( + dam_tbv_fat: float, + dam_tbv_protein: float, + birth_year_month: str, + top_listing_semen_fat: float, + top_listing_semen_protein: float, + expected_mean_tbv_fat: float, + expected_mean_tbv_protein: float, + expected_std_tbv_fat: float, + expected_std_tbv_protein: float, + genetics: Genetics, + mocker: MockerFixture, ) -> None: - mock_generate_random_number = mocker.patch.object( - Utility, "generate_random_number", side_effect=lambda avg, std: (avg, std) + """Unit test for _calculate_newborn_calf_tbv_values()""" + AnimalConfig.top_listing_semen["estimated_fat"] = {birth_year_month: top_listing_semen_fat} + AnimalConfig.top_listing_semen["estimated_protein"] = {birth_year_month: top_listing_semen_protein} + mock_generate_bivariate_random_numbers = mocker.patch.object( + Utility, "generate_bivariate_random_numbers", return_value=(10.0, 20.0) ) - mock_clamp_date = mocker.patch.object( - AnimalGenetics, - "_clamp_birth_year_month_in_data_range", - wraps=AnimalGenetics._clamp_birth_year_month_in_data_range, + + genetics._calculate_newborn_calf_tbv_values(dam_tbv_fat, dam_tbv_protein, birth_year_month) + + mock_generate_bivariate_random_numbers.assert_called_once_with( + expected_mean_tbv_fat, + expected_mean_tbv_protein, + expected_std_tbv_fat, + expected_std_tbv_protein, + TBV_CORRELATION, ) - mock_time = mocker.MagicMock(auto_spec=RufasTime) - mock_time.current_calendar_year = int(birth_date[:4]) - mock_time.current_month = int(birth_date[5:7]) - setup_animal_genetics(mocker) +def test_calculate_ep_values(genetics: Genetics, mocker: MockerFixture) -> None: + """Unit test for _calculate_ep_values()""" + mock_generate_bivariate_random_numbers = mocker.patch.object( + Utility, "generate_bivariate_random_numbers", return_value=(10.0, 20.0) + ) + genetics._calculate_ep_values() + mock_generate_bivariate_random_numbers.assert_called_once_with( + 0.0, 0.0, E_PERMANENT_FAT_STD, E_PERMANENT_PROTEIN_STD, E_PERMANENT_CORRELATION + ) - expected_mean = dam_net_merit_value + AnimalGenetics.top_semen[breed.name][birth_date[:7]] - expected_std = np.sqrt((AnimalGenetics.net_merit[breed.name][birth_date[:7]]["std"] ** 2) / 2) - result = AnimalGenetics.assign_net_merit_value_to_newborn_calf(mock_time, breed, dam_net_merit_value) - assert result == (expected_mean, expected_std) - mock_generate_random_number.assert_called_once_with(expected_mean, expected_std) - assert mock_clamp_date.call_count == 2 +def test_calculate_et_values(genetics: Genetics, mocker: MockerFixture) -> None: + """Unit test for _calculate_et_values()""" + mock_generate_bivariate_random_numbers = mocker.patch.object( + Utility, "generate_bivariate_random_numbers", return_value=(10.0, 20.0) + ) + genetics._calculate_et_values() + mock_generate_bivariate_random_numbers.assert_called_once_with( + 0.0, 0.0, E_TEMPORARY_FAT_STD, E_TEMPORARY_PROTEIN_STD, E_TEMPORARY_CORRELATION + ) @pytest.mark.parametrize( - "birth_year_month, is_for_net_merit, expected_year_month", + "mean_phenotype_fat, mean_phenotype_protein, birth_year," + "tbv_fat, tbv_protein, e_p_fat, e_p_protein, e_t_fat, e_t_protein," + "expected_phenotype_fat, expected_phenotype_protein", [ - ("2008-01", True, "2008-01"), - ("2008-01", False, "2008-01"), - ("2000-01", True, "2006-02"), - ("2000-01", False, "2006-01"), - ("2030-01", True, "2024-12"), - ("2030-01", False, "2024-12"), + (10.0, 20.0, 2023, 100.0, 200.0, 5.0, 10.0, 3.0, 6.0, 118.0, 236.0), + (15.0, 25.0, 2022, 120.0, 240.0, 7.0, 14.0, 4.0, 8.0, 146.0, 287.0), ], ) -def test_clamp_birth_year_month_in_data_range( - birth_year_month: str, is_for_net_merit: bool, expected_year_month: str +def test_calculate_phenotype_values( + mean_phenotype_fat: float, + mean_phenotype_protein: float, + birth_year: int, + tbv_fat: float, + tbv_protein: float, + e_p_fat: float, + e_p_protein: float, + e_t_fat: float, + e_t_protein: float, + expected_phenotype_fat: float, + expected_phenotype_protein: float, + genetics: Genetics, ) -> None: - """Tests that birth dates of cows are correctly clamped within supported range.""" - AnimalGenetics.year_month_of_first_net_merit_value = "2006-02" - AnimalGenetics.year_month_of_first_top_semen_value = "2006-01" - AnimalGenetics.year_month_of_last_net_merit_value = "2024-12" - AnimalGenetics.year_month_of_last_top_semen_value = "2024-12" + """Unit test for _calculate_phenotype_values()""" + genetics.TBV_fat = tbv_fat + genetics.TBV_protein = tbv_protein + genetics.E_permanent_fat = e_p_fat + genetics.E_permanent_protein = e_p_protein + genetics.E_temporary_fat = e_t_fat + genetics.E_temporary_protein = e_t_protein - actual = AnimalGenetics._clamp_birth_year_month_in_data_range(birth_year_month, is_for_net_merit) + AnimalConfig.average_phenotype["fat_kg"] = {birth_year: mean_phenotype_fat} + AnimalConfig.average_phenotype["protein_kg"] = {birth_year: mean_phenotype_protein} - assert actual == expected_year_month + phenotype_fat, phenotype_protein = genetics._calculate_phenotype_values(birth_year=birth_year) + + assert phenotype_fat == pytest.approx(expected_phenotype_fat) + assert phenotype_protein == pytest.approx(expected_phenotype_protein) @pytest.mark.parametrize( - "birth_year_month, is_for_net_merit, expected_year_month", + "animal_type, parity, tbv_fat, tbv_protein," + "expected_std_ebv_fat, expected_std_ebv_protein, expected_ebv_fat, expected_ebv_protein", [ - ("2000-01", True, "2006-02"), - ("2000-01", False, "2006-01"), - ("2030-01", True, "2024-12"), - ("2030-01", False, "2024-12"), + (AnimalType.CALF, None, 10.0, 20.0, 2.519765614099851, 1.8159450019204877, 6.20625, 12.3125), + (AnimalType.HEIFER_I, None, 10.0, 20.0, 2.519765614099851, 1.8159450019204877, 6.20625, 12.3125), + (AnimalType.HEIFER_II, None, 10.0, 20.0, 2.519765614099851, 1.8159450019204877, 6.20625, 12.3125), + (AnimalType.HEIFER_III, None, 10.0, 20.0, 2.519765614099851, 1.8159450019204877, 6.20625, 12.3125), + (AnimalType.LAC_COW, 1, 15.0, 25.0, 2.4380976190464563, 1.75708850090142, 10.096, 16.892), + ( + AnimalType.DRY_COW, + 2, + 15.0, + 25.0, + 2.274365570879053, + 1.6390900676899975, + 11.24275, + 18.773, + ), + (AnimalType.LAC_COW, 3, 15.0, 25.0, 1.992641462983243, 1.4360571019287496, 12.459, 20.768), + (AnimalType.DRY_COW, 4, 15.0, 25.0, 1.992641462983243, 1.4360571019287496, 12.459, 20.768), ], ) -def test_clamp_birth_year_month_in_data_range_error( - birth_year_month: str, is_for_net_merit: bool, expected_year_month: str, mocker: MockerFixture +def test_calculate_ebv_values( + animal_type: AnimalType, + parity: int, + tbv_fat: float, + tbv_protein: float, + expected_std_ebv_fat: float, + expected_std_ebv_protein: float, + expected_ebv_fat: float, + expected_ebv_protein: float, + genetics: Genetics, + mocker: MockerFixture, ) -> None: - """Tests that error is raised when birth date of a cow is not in the available genetic data.""" - AnimalGenetics.year_month_of_first_net_merit_value = "2006-02" - AnimalGenetics.year_month_of_first_top_semen_value = "2006-01" - AnimalGenetics.year_month_of_last_net_merit_value = "2024-12" - AnimalGenetics.year_month_of_last_top_semen_value = "2024-12" + """Unit test for _calculate_ebv_values()""" + genetics.TBV_fat = tbv_fat + genetics.TBV_protein = tbv_protein + mock_np_random_normal = mocker.patch.object(numpy.random, "normal", side_effect=[0.1, 0.1]) + ebv_fat, ebv_protein = genetics._calculate_ebv_values( + animal_type=animal_type, parity=parity, group_specific_TBV_fat_mean=1.1, group_specific_TBV_protein_mean=2.2 + ) - om = OutputManager() - mock_add_error = mocker.patch.object(om, "add_error") + assert mock_np_random_normal.call_args_list == [ + call(0.0, pytest.approx(expected_std_ebv_fat)), + call(0.0, pytest.approx(expected_std_ebv_protein)), + ] + assert ebv_fat == pytest.approx(expected_ebv_fat) + assert ebv_protein == pytest.approx(expected_ebv_protein) - actual = AnimalGenetics._clamp_birth_year_month_in_data_range(birth_year_month, is_for_net_merit) - assert actual == expected_year_month - mock_add_error.assert_called_once() +@pytest.mark.parametrize( + "ebv_fat, ebv_protein, expected_ranking_index", + [ + (5.725, 11.35, 3.29605), + (1.234, 5.678, 1.130552), + ], +) +def test_calculate_ranking_index( + ebv_fat: float, ebv_protein: float, expected_ranking_index: float, genetics: Genetics +) -> None: + """Unit test for _calculate_ranking_index()""" + genetics.EBV_fat = ebv_fat + genetics.EBV_protein = ebv_protein + ranking_index = genetics._calculate_ranking_index() + assert ranking_index == pytest.approx(expected_ranking_index) diff --git a/tests/test_biophysical/test_animal/test_animal/test_animal.py b/tests/test_biophysical/test_animal/test_animal/test_animal.py index 87d44f349e..5444c5827b 100644 --- a/tests/test_biophysical/test_animal/test_animal/test_animal.py +++ b/tests/test_biophysical/test_animal/test_animal/test_animal.py @@ -59,6 +59,20 @@ from RUFAS.rufas_time import RufasTime +@pytest.fixture +def mock_time() -> RufasTime: + mock_time = MagicMock(spec=RufasTime) + mock_time.current_date = (dummy_date := datetime(2023, 1, 1)) + mock_time.simulation_day = 18 + AnimalConfig.top_listing_semen["estimated_fat"] = {f"{dummy_date.year}-{dummy_date.month:02d}": 10.0} + AnimalConfig.top_listing_semen["estimated_protein"] = {f"{dummy_date.year}-{dummy_date.month:02d}": 10.0} + AnimalConfig.average_phenotype["fat_kg"] = {year: 10.0 for year in range(dummy_date.year - 10, dummy_date.year + 1)} + AnimalConfig.average_phenotype["protein_kg"] = { + year: 10.0 for year in range(dummy_date.year - 10, dummy_date.year + 1) + } + return mock_time + + def mock_submodule_init(mocker: MockerFixture) -> None: mocker.patch("RUFAS.biophysical.animal.growth.growth.Growth", return_value=MagicMock(auto_spec=Growth)) @@ -110,7 +124,6 @@ def assert_animal_init_properties( assert result.animal_type == AnimalType(args["animal_type"]) assert result.days_born == args["days_born"] assert result.birth_weight == args["birth_weight"] - assert result.net_merit == args["net_merit"] assert result.cull_reason == "" @@ -124,12 +137,13 @@ def assert_animal_init_properties( birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, initial_phosphorus=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, ) ], ) -def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixture) -> None: +def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime) -> None: mock_submodule_init(mocker) ( @@ -139,7 +153,7 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu mock_initialize_cow, ) = mock_animal_init_methods(mocker) - result = Animal(args) + result = Animal(args, mock_time) assert_animal_init_properties(result, args) @@ -160,8 +174,9 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, initial_phosphorus=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, ), "conventional", Sex.FEMALE, @@ -176,8 +191,9 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, initial_phosphorus=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, ), "sexed", Sex.FEMALE, @@ -192,8 +208,9 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, initial_phosphorus=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, ), "conventional", Sex.MALE, @@ -208,7 +225,8 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, initial_phosphorus=10.0, ), "sexed", @@ -224,7 +242,8 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, initial_phosphorus=10.0, ), "random", @@ -240,7 +259,8 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, initial_phosphorus=10.0, ), "conventional", @@ -256,7 +276,8 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, initial_phosphorus=10.0, ), "sexed", @@ -272,7 +293,8 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, initial_phosphorus=10.0, ), "conventional", @@ -288,7 +310,8 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu birth_date="2023-01-01", days_born=10, birth_weight=10.0, - net_merit=10.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, initial_phosphorus=10.0, ), "sexed", @@ -299,14 +322,20 @@ def test_init_newborn_calf(args: NewBornCalfValuesTypedDict, mocker: MockerFixtu ], ) def test_initialize_newborn_calf( - args: NewBornCalfValuesTypedDict, semen_type: str, sex: Sex, culled: bool, sold: bool, mocker: MockerFixture + args: NewBornCalfValuesTypedDict, + semen_type: str, + sex: Sex, + culled: bool, + sold: bool, + mocker: MockerFixture, + mock_time: RufasTime, ) -> None: original_semen_type = AnimalConfig.semen_type AnimalConfig.semen_type = semen_type if not (semen_type in ["conventional", "sexed"]): with pytest.raises(ValueError): - Animal(args) + Animal(args, mock_time) return male_calf_rate = ( AnimalConfig.male_calf_rate_conventional_semen @@ -323,7 +352,7 @@ def test_initialize_newborn_calf( ) mock_rvs = mocker.patch("RUFAS.biophysical.animal.animal.truncnorm.rvs", return_value=600) - animal = Animal(args) + animal = Animal(args, mock_time) assert animal.sex == sex assert animal.sold == sold assert animal.birth_weight == args["birth_weight"] @@ -350,7 +379,6 @@ def test_initialize_newborn_calf( animal_type="Calf", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -358,7 +386,7 @@ def test_initialize_newborn_calf( ) ], ) -def test_init_calf(args: CalfValuesTypedDict, mocker: MockerFixture) -> None: +def test_init_calf(args: CalfValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime) -> None: mock_submodule_init(mocker) ( @@ -368,7 +396,7 @@ def test_init_calf(args: CalfValuesTypedDict, mocker: MockerFixture) -> None: mock_initialize_cow, ) = mock_animal_init_methods(mocker) - result = Animal(args) + result = Animal(args, mock_time) assert_animal_init_properties(result, args) @@ -387,7 +415,6 @@ def test_init_calf(args: CalfValuesTypedDict, mocker: MockerFixture) -> None: animal_type="HeiferI", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -395,7 +422,7 @@ def test_init_calf(args: CalfValuesTypedDict, mocker: MockerFixture) -> None: ) ], ) -def test_init_heiferI(args: HeiferIValuesTypedDict, mocker: MockerFixture) -> None: +def test_init_heiferI(args: HeiferIValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime) -> None: mock_submodule_init(mocker) ( @@ -405,7 +432,7 @@ def test_init_heiferI(args: HeiferIValuesTypedDict, mocker: MockerFixture) -> No mock_initialize_cow, ) = mock_animal_init_methods(mocker) - result = Animal(args) + result = Animal(args, mock_time) assert_animal_init_properties(result, args) @@ -424,7 +451,6 @@ def test_init_heiferI(args: HeiferIValuesTypedDict, mocker: MockerFixture) -> No animal_type="Calf", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -436,7 +462,6 @@ def test_init_heiferI(args: HeiferIValuesTypedDict, mocker: MockerFixture) -> No animal_type="HeiferI", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -444,12 +469,14 @@ def test_init_heiferI(args: HeiferIValuesTypedDict, mocker: MockerFixture) -> No ), ], ) -def test_initialize_calf_or_heiferI(args: CalfValuesTypedDict | HeiferIValuesTypedDict, mocker: MockerFixture) -> None: +def test_initialize_calf_or_heiferI( + args: CalfValuesTypedDict | HeiferIValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime +) -> None: mock_init_events_from_string = mocker.patch( "RUFAS.biophysical.animal.data_types.animal_events.AnimalEvents.init_from_string" ) - animal = Animal(args) + animal = Animal(args, mock_time) assert animal.sex == Sex.FEMALE assert animal.sold is False assert animal.birth_weight == args["birth_weight"] @@ -468,7 +495,6 @@ def test_initialize_calf_or_heiferI(args: CalfValuesTypedDict | HeiferIValuesTyp animal_type="HeiferII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -478,7 +504,7 @@ def test_initialize_calf_or_heiferI(args: CalfValuesTypedDict | HeiferIValuesTyp ) ], ) -def test_init_heiferII(args: HeiferIIValuesTypedDict, mocker: MockerFixture) -> None: +def test_init_heiferII(args: HeiferIIValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime) -> None: mock_submodule_init(mocker) ( @@ -488,7 +514,7 @@ def test_init_heiferII(args: HeiferIIValuesTypedDict, mocker: MockerFixture) -> mock_initialize_cow, ) = mock_animal_init_methods(mocker) - result = Animal(args) + result = Animal(args, mock_time) assert_animal_init_properties(result, args) @@ -507,7 +533,6 @@ def test_init_heiferII(args: HeiferIIValuesTypedDict, mocker: MockerFixture) -> animal_type="HeiferII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -517,7 +542,7 @@ def test_init_heiferII(args: HeiferIIValuesTypedDict, mocker: MockerFixture) -> ) ], ) -def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture) -> None: +def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime) -> None: mock_submodule_init(mocker) ( @@ -527,7 +552,7 @@ def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture) - mock_initialize_cow, ) = mock_animal_init_methods(mocker) - result = Animal(args) + result = Animal(args, mock_time) assert_animal_init_properties(result, args) @@ -546,7 +571,6 @@ def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture) - animal_type="HeiferII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -560,7 +584,6 @@ def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture) - animal_type="HeiferIII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -574,7 +597,6 @@ def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture) - animal_type="HeiferII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -589,7 +611,6 @@ def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture) - animal_type="HeiferIII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -601,7 +622,7 @@ def test_init_heiferIII(args: HeiferIIIValuesTypedDict, mocker: MockerFixture) - ], ) def test_initialize_heiferII_or_heiferIII( - args: HeiferIIValuesTypedDict | HeiferIIIValuesTypedDict, mocker: MockerFixture + args: HeiferIIValuesTypedDict | HeiferIIIValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime ) -> None: mock_init_events_from_string = mocker.patch( "RUFAS.biophysical.animal.data_types.animal_events.AnimalEvents.init_from_string" @@ -618,7 +639,7 @@ def test_initialize_heiferII_or_heiferIII( else HeiferSynchEDSubProtocol(args["heifer_reproduction_sub_protocol"]) ) - animal = Animal(args) + animal = Animal(args, mock_time) assert animal.days_in_pregnancy == expected_days_in_pregnancy assert animal.nutrients.phosphorus_for_gestation_required_for_calf == expected_p_calf @@ -649,7 +670,6 @@ def test_initialize_heiferII_or_heiferIII( animal_type="DryCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -668,7 +688,6 @@ def test_initialize_heiferII_or_heiferIII( animal_type="LacCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -683,7 +702,7 @@ def test_initialize_heiferII_or_heiferIII( ), ], ) -def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: +def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime) -> None: mock_submodule_init(mocker) ( @@ -693,7 +712,7 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: mock_initialize_cow, ) = mock_animal_init_methods(mocker) - result = Animal(args) + result = Animal(args, mock_time) assert_animal_init_properties(result, args) @@ -712,7 +731,6 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: animal_type="DryCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -731,7 +749,6 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: animal_type="LacCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -750,7 +767,6 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: animal_type="DryCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -769,7 +785,6 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: animal_type="LacCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -789,7 +804,6 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: animal_type="DryCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -809,7 +823,6 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: animal_type="LacCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -825,7 +838,9 @@ def test_init_cow(args: CowValuesTypedDict, mocker: MockerFixture) -> None: ), ], ) -def test_initialize_cow(args: HeiferIIValuesTypedDict | HeiferIIIValuesTypedDict, mocker: MockerFixture) -> None: +def test_initialize_cow( + args: HeiferIIValuesTypedDict | HeiferIIIValuesTypedDict, mocker: MockerFixture, mock_time: RufasTime +) -> None: mock_init_events_from_string = mocker.patch( "RUFAS.biophysical.animal.data_types.animal_events.AnimalEvents.init_from_string" ) @@ -836,7 +851,7 @@ def test_initialize_cow(args: HeiferIIValuesTypedDict | HeiferIIIValuesTypedDict expected_calves = args.get("parity", 0) expected_calving_interval = args.get("calving_interval", AnimalConfig.calving_interval) - animal = Animal(args) + animal = Animal(args, mock_time) assert animal.days_in_milk == expected_days_in_milk assert animal.reproduction.calves == expected_calves @@ -846,41 +861,39 @@ def test_initialize_cow(args: HeiferIIValuesTypedDict | HeiferIIIValuesTypedDict @pytest.fixture -def mock_calf() -> Animal: +def mock_calf(mock_time: RufasTime) -> Animal: args = CalfValuesTypedDict( id=1, breed="HO", animal_type="Calf", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, events="", ) - return Animal(args) + return Animal(args, mock_time) @pytest.fixture -def mock_heiferI() -> Animal: +def mock_heiferI(mock_time: RufasTime) -> Animal: args = HeiferIValuesTypedDict( id=1, breed="HO", animal_type="HeiferI", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, events="", ) - return Animal(args) + return Animal(args, mock_time) @pytest.fixture -def mock_heiferII(mocker: MockerFixture) -> Animal: +def mock_heiferII(mocker: MockerFixture, mock_time: RufasTime) -> Animal: mocker.patch("RUFAS.biophysical.animal.reproduction.reproduction.Reproduction.__init__", return_value=None) args = HeiferIIValuesTypedDict( id=1, @@ -888,7 +901,6 @@ def mock_heiferII(mocker: MockerFixture) -> Animal: animal_type="HeiferII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -897,11 +909,11 @@ def mock_heiferII(mocker: MockerFixture) -> Animal: heifer_reproduction_sub_protocol="5dCG2P", days_in_pregnancy=10, ) - return Animal(args) + return Animal(args, mock_time) @pytest.fixture -def mock_heiferIII(mocker: MockerFixture) -> Animal: +def mock_heiferIII(mocker: MockerFixture, mock_time: RufasTime) -> Animal: mocker.patch("RUFAS.biophysical.animal.reproduction.reproduction.Reproduction.__init__", return_value=None) args = HeiferIIIValuesTypedDict( id=1, @@ -909,7 +921,6 @@ def mock_heiferIII(mocker: MockerFixture) -> Animal: animal_type="HeiferIII", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -918,11 +929,11 @@ def mock_heiferIII(mocker: MockerFixture) -> Animal: heifer_reproduction_sub_protocol="5dCG2P", days_in_pregnancy=10, ) - return Animal(args) + return Animal(args, mock_time) @pytest.fixture -def mock_lactating_cow(mocker: MockerFixture) -> Animal: +def mock_lactating_cow(mocker: MockerFixture, mock_time: RufasTime) -> Animal: mocker.patch("RUFAS.biophysical.animal.reproduction.reproduction.Reproduction.__init__", return_value=None) args = CowValuesTypedDict( id=1, @@ -930,7 +941,6 @@ def mock_lactating_cow(mocker: MockerFixture) -> Animal: animal_type="LacCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -944,11 +954,11 @@ def mock_lactating_cow(mocker: MockerFixture) -> Animal: calf_birth_weight=15.0, days_in_milk=10, ) - return Animal(args) + return Animal(args, mock_time) @pytest.fixture -def mock_dry_cow(mocker: MockerFixture) -> Animal: +def mock_dry_cow(mocker: MockerFixture, mock_time: RufasTime) -> Animal: mocker.patch("RUFAS.biophysical.animal.reproduction.reproduction.Reproduction.__init__", return_value=None) args = CowValuesTypedDict( id=1, @@ -956,7 +966,6 @@ def mock_dry_cow(mocker: MockerFixture) -> Animal: animal_type="DryCow", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -970,14 +979,13 @@ def mock_dry_cow(mocker: MockerFixture) -> Animal: calf_birth_weight=15.0, parity=3, ) - return Animal(args) + return Animal(args, mock_time) -def test_setup_lactation_curve_parameters(mocker: MockerFixture) -> None: +def test_setup_lactation_curve_parameters(mocker: MockerFixture, mock_time: RufasTime) -> None: mock_set_lactation_parameters = mocker.patch( "RUFAS.biophysical.animal.milk.lactation_curve.LactationCurve.set_lactation_parameters" ) - mock_time = mocker.MagicMock(auto_spec=RufasTime) Animal.setup_lactation_curve_parameters(time=mock_time) @@ -2773,7 +2781,6 @@ def test_get_calf_values(mock_calf: Animal) -> None: animal_type="Calf", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -2789,7 +2796,6 @@ def test_get_heiferI_values(mock_heiferI: Animal) -> None: animal_type="HeiferI", days_born=10, birth_weight=10.0, - net_merit=10.0, mature_body_weight=10.0, body_weight=12.3, wean_weight=10.0, @@ -2829,7 +2835,6 @@ def test_get_heiferII_values(mock_heiferII: Animal) -> None: heifer_reproduction_sub_protocol="5dCG2P", id=1, mature_body_weight=10.0, - net_merit=10.0, phosphorus_for_gestation_required_for_calf=1, wean_weight=10.0, ) @@ -2867,7 +2872,6 @@ def test_get_heiferIII_values(mock_heiferIII: Animal) -> None: heifer_reproduction_sub_protocol="5dCG2P", id=1, mature_body_weight=10.0, - net_merit=10.0, phosphorus_for_gestation_required_for_calf=1, wean_weight=10.0, ) @@ -2905,7 +2909,6 @@ def test_get_cow_values(mock_lactating_cow: Animal) -> None: heifer_reproduction_sub_protocol="5dCG2P", id=1, mature_body_weight=10.0, - net_merit=10.0, phosphorus_for_gestation_required_for_calf=1, wean_weight=10.0, calving_interval=400, diff --git a/tests/test_biophysical/test_animal/test_animal/test_animal_config.py b/tests/test_biophysical/test_animal/test_animal/test_animal_config.py index a847c26e0e..dd9b0dfa90 100644 --- a/tests/test_biophysical/test_animal/test_animal/test_animal_config.py +++ b/tests/test_biophysical/test_animal/test_animal/test_animal_config.py @@ -193,6 +193,10 @@ def get_data_side_effect(key: str) -> Any: return animal_data if key == "feed.milk_reduction_maximum": return 1.23 + if key == "animal_mean_phenotype": + return {} + if key == "animal_top_listing_semen": + return {} raise KeyError(key) mock_im.get_data.side_effect = get_data_side_effect @@ -350,6 +354,10 @@ def get_data_side_effect(key: str) -> Any: return animal_data elif key == "feed.milk_reduction_maximum": return 2.5 + if key == "animal_mean_phenotype": + return {} + if key == "animal_top_listing_semen": + return {} raise KeyError(key) mock_im.get_data.side_effect = get_data_side_effect diff --git a/tests/test_biophysical/test_animal/test_animal/test_herd_factory.py b/tests/test_biophysical/test_animal/test_animal/test_herd_factory.py index 38fea86fa3..71c2c128d8 100644 --- a/tests/test_biophysical/test_animal/test_animal/test_herd_factory.py +++ b/tests/test_biophysical/test_animal/test_animal/test_herd_factory.py @@ -29,10 +29,6 @@ def mock_herd_factory(mocker: MockerFixture) -> HerdFactory: """Returns an HerdFactory object""" mocker.patch("RUFAS.rufas_time.RufasTime.__init__", return_value=None) mock_im_get_data([], mocker) - mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.initialize_class_variables", - return_value=None, - ) return HerdFactory() @@ -80,10 +76,6 @@ def test_init( """Unit test for __init__()""" mock_time_init = mocker.patch("RUFAS.rufas_time.RufasTime.__init__", return_value=None) mock_get_data = mock_im_get_data([], mocker) - mock_animal_genetics_initialize_class_variables = mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.initialize_class_variables", - return_value=None, - ) mock_animal_population_init = mocker.patch( "RUFAS.biophysical.animal.data_types.animal_population.AnimalPopulation.__init__", return_value=None, @@ -98,7 +90,6 @@ def test_init( call("animal.herd_initialization.initial_animal_num"), call("animal.herd_initialization.simulation_days"), ] - mock_animal_genetics_initialize_class_variables.assert_called_once_with() assert mock_animal_population_init.call_args_list == [ call( calves=[], @@ -458,12 +449,6 @@ def test_cow_give_birth(is_calf_sold: bool, mock_herd_factory: HerdFactory, mock mock_time = MagicMock(auto_spec=RufasTime) mock_herd_factory.time = mock_time - mock_genetics_assign_net_merit_value_to_newborn_calf = mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics." - "AnimalGenetics.assign_net_merit_value_to_newborn_calf", - return_value=108.8, - ) - mock_pre_animal_population = AnimalPopulation( calves=[], heiferIs=[], @@ -491,15 +476,13 @@ def test_cow_give_birth(is_calf_sold: bool, mock_herd_factory: HerdFactory, mock days_born=0, initial_phosphorus=8.8, birth_weight=18.8, - net_merit=0.0, animal_type=AnimalType.CALF.value, - ) + ), + mock_time, ) if is_calf_sold: - mock_genetics_assign_net_merit_value_to_newborn_calf.assert_not_called() assert mock_herd_factory.pre_animal_population.calves == [] else: - mock_genetics_assign_net_merit_value_to_newborn_calf.assert_called_once_with(mock_time, Breed.HO, 99.9) assert mock_herd_factory.pre_animal_population.calves == [mock_calf] @@ -798,11 +781,6 @@ def test_generate_animals( mock_time.current_date = datetime.today() mocker.patch("RUFAS.input_manager.InputManager.get_data", return_value=None) - mock_genetics_assign_net_merit_value_to_newborn_calf = mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics." - "AnimalGenetics.assign_net_merit_value_to_animals_entering_herd", - return_value=108.8, - ) mock_herd_factory.time = mock_time mock_herd_factory.breed = Breed.HO @@ -834,12 +812,8 @@ def test_generate_animals( assert mock_cows_update.call_count == simulation_days if is_calf_sold: - assert mock_genetics_assign_net_merit_value_to_newborn_calf.call_count == 0 - assert mock_genetics_assign_net_merit_value_to_newborn_calf.call_count == 0 assert len(result.calves) == 0 else: - assert mock_genetics_assign_net_merit_value_to_newborn_calf.call_count == initial_animal_num - assert mock_genetics_assign_net_merit_value_to_newborn_calf.call_count == initial_animal_num assert len(result.calves) == initial_animal_num @@ -883,20 +857,10 @@ def test_init_animal_from_data( dummy_animal_id = 31415 dummy_animal_data = {"dummy": "data", "breed": "dummy", "days_born": 15} - mock_genetics_assignment = mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics." - "AnimalGenetics.assign_net_merit_value_to_animals_entering_herd", - return_value=0.0, - ) - mocked_animal = MagicMock(auto_spec=Animal) mocked_animal.breed = Breed.HO mock_animal_init = mocker.patch("RUFAS.biophysical.animal.herd_factory.Animal", return_value=mocked_animal) - mock_backtrack_animal_birth_date = mocker.patch.object( - mock_herd_factory, "_backtrack_animal_birth_date", return_value="" - ) - mock_pre_animal_population = MagicMock(auto_spec=AnimalPopulation) mock_pre_animal_population.next_id.return_value = dummy_animal_id @@ -909,9 +873,7 @@ def test_init_animal_from_data( if animal_type == "calf": dummy_animal_data.update(p_init=0) - mock_animal_init.assert_called_once_with(dummy_animal_data) - mock_backtrack_animal_birth_date.assert_called_once_with(dummy_animal_data["days_born"], mock_time) - mock_genetics_assignment.assert_called_once_with(birth_date="", breed=Breed.HO) + mock_animal_init.assert_called_once_with(dummy_animal_data, mock_time) assert result == mocked_animal @@ -931,10 +893,6 @@ def test_initialize_herd_from_data( ) -> None: """Unit test for _init_herd_from_data()""" mocker.patch("RUFAS.rufas_time.RufasTime.__init__", return_value=None) - mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.initialize_class_variables", - return_value=None, - ) mock_get_data = mock_im_get_data( [ { @@ -1104,10 +1062,6 @@ def test_random_sample_with_replacement_by_type( } mocker.patch("RUFAS.rufas_time.RufasTime.__init__", return_value=None) - mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.initialize_class_variables", - return_value=None, - ) mock_get_data = mock_im_get_data([post_num], mocker) herd_factory = HerdFactory() @@ -1293,10 +1247,6 @@ def test_random_sample_with_replacement_by_type_replacement( ) -> None: """Unit test for _random_sample_with_replacement_by_type() with replacement cows""" mocker.patch("RUFAS.rufas_time.RufasTime.__init__", return_value=None) - mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.initialize_class_variables", - return_value=None, - ) mock_get_data = mock_im_get_data([post_num], mocker) herd_factory = HerdFactory() diff --git a/tests/test_biophysical/test_animal/test_animal_module_reporter/test_animal_module_reporter.py b/tests/test_biophysical/test_animal/test_animal_module_reporter/test_animal_module_reporter.py index 2a44ea6775..0d9e01b3cc 100644 --- a/tests/test_biophysical/test_animal/test_animal_module_reporter/test_animal_module_reporter.py +++ b/tests/test_biophysical/test_animal/test_animal_module_reporter/test_animal_module_reporter.py @@ -10,6 +10,7 @@ from RUFAS.biophysical.animal.data_types.animal_manure_excretions import AnimalManureExcretions from RUFAS.biophysical.animal.data_types.animal_population import AnimalPopulationStatistics from RUFAS.biophysical.animal.data_types.animal_typed_dicts import SoldAnimalTypedDict, StillbornCalfTypedDict +from RUFAS.biophysical.animal.data_types.animal_types import AnimalType from RUFAS.biophysical.animal.data_types.herd_statistics import HerdStatistics from RUFAS.biophysical.animal.data_types.milk_production import MilkProductionStatistics from RUFAS.biophysical.animal.data_types.nutrition_data_structures import ( @@ -84,6 +85,20 @@ def test_report_milk(mocker: MockerFixture) -> None: milk_fat=3.4, milk_lactose=5.6, parity=1, + days_born=10, + days_in_pregnancy=10, + animal_type=AnimalType.LAC_COW, + TBV_fat=10.0, + TBV_protein=10.0, + E_permanent_fat=10.0, + E_permanent_protein=10.0, + E_temporary_fat=10.0, + E_temporary_protein=10.0, + phenotype_fat=10.0, + phenotype_protein=10.0, + EBV_fat=10.0, + EBV_protein=10.0, + ranking_index=10.0, ), MilkProductionStatistics( cow_id=2, @@ -94,6 +109,20 @@ def test_report_milk(mocker: MockerFixture) -> None: milk_fat=73, milk_lactose=7.9, parity=5, + days_born=10, + days_in_pregnancy=10, + animal_type=AnimalType.LAC_COW, + TBV_fat=10.0, + TBV_protein=10.0, + E_permanent_fat=10.0, + E_permanent_protein=10.0, + E_temporary_fat=10.0, + E_temporary_protein=10.0, + phenotype_fat=10.0, + phenotype_protein=10.0, + EBV_fat=10.0, + EBV_protein=10.0, + ranking_index=10.0, ), MilkProductionStatistics( cow_id=12345, @@ -104,6 +133,20 @@ def test_report_milk(mocker: MockerFixture) -> None: milk_fat=0, milk_lactose=0, parity=2, + days_born=10, + days_in_pregnancy=10, + animal_type=AnimalType.LAC_COW, + TBV_fat=10.0, + TBV_protein=10.0, + E_permanent_fat=10.0, + E_permanent_protein=10.0, + E_temporary_fat=10.0, + E_temporary_protein=10.0, + phenotype_fat=10.0, + phenotype_protein=10.0, + EBV_fat=10.0, + EBV_protein=10.0, + ranking_index=10.0, ), ] @@ -127,6 +170,20 @@ def test_report_milk(mocker: MockerFixture) -> None: "parity": 1, "is_milking": True, "simulation_day": simulation_day, + "days_born": 10, + "days_in_pregnancy": 10, + "animal_type": AnimalType.LAC_COW.name, + "TBV_fat": 10.0, + "TBV_protein": 10.0, + "E_permanent_fat": 10.0, + "E_permanent_protein": 10.0, + "E_temporary_fat": 10.0, + "E_temporary_protein": 10.0, + "phenotype_fat": 10.0, + "phenotype_protein": 10.0, + "EBV_fat": 10.0, + "EBV_protein": 10.0, + "ranking_index": 10.0, }, info_map, ), @@ -143,6 +200,20 @@ def test_report_milk(mocker: MockerFixture) -> None: "parity": 5, "is_milking": True, "simulation_day": simulation_day, + "days_born": 10, + "days_in_pregnancy": 10, + "animal_type": AnimalType.LAC_COW.name, + "TBV_fat": 10.0, + "TBV_protein": 10.0, + "E_permanent_fat": 10.0, + "E_permanent_protein": 10.0, + "E_temporary_fat": 10.0, + "E_temporary_protein": 10.0, + "phenotype_fat": 10.0, + "phenotype_protein": 10.0, + "EBV_fat": 10.0, + "EBV_protein": 10.0, + "ranking_index": 10.0, }, info_map, ), @@ -159,6 +230,20 @@ def test_report_milk(mocker: MockerFixture) -> None: "parity": 2, "is_milking": False, "simulation_day": simulation_day, + "days_born": 10, + "days_in_pregnancy": 10, + "animal_type": AnimalType.LAC_COW.name, + "TBV_fat": 10.0, + "TBV_protein": 10.0, + "E_permanent_fat": 10.0, + "E_permanent_protein": 10.0, + "E_temporary_fat": 10.0, + "E_temporary_protein": 10.0, + "phenotype_fat": 10.0, + "phenotype_protein": 10.0, + "EBV_fat": 10.0, + "EBV_protein": 10.0, + "ranking_index": 10.0, }, info_map, ), @@ -778,6 +863,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), SoldAnimalTypedDict( id=2, @@ -787,6 +873,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), SoldAnimalTypedDict( id=3, @@ -796,6 +883,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), ] herd_statistics.sold_heiferIIs_info = [ @@ -807,6 +895,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), SoldAnimalTypedDict( id=5, @@ -816,6 +905,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), SoldAnimalTypedDict( id=6, @@ -825,6 +915,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), ] herd_statistics.sold_heiferIIIs_info = [ @@ -836,6 +927,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), SoldAnimalTypedDict( id=8, @@ -845,6 +937,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), SoldAnimalTypedDict( id=9, @@ -854,6 +947,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history="", ), ] herd_statistics.sold_and_died_cows_info = [ @@ -865,6 +959,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason=animal_constants.UDDER_CULL, days_in_milk=18, parity=2, + genetic_history="", ), SoldAnimalTypedDict( id=11, @@ -874,6 +969,7 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason=animal_constants.DEATH_CULL, days_in_milk=88, parity=1, + genetic_history="", ), SoldAnimalTypedDict( id=12, @@ -883,11 +979,12 @@ def test_report_sold_animal_information(mocker: MockerFixture) -> None: cull_reason=animal_constants.LAMENESS_CULL, days_in_milk=0, parity=3, + genetic_history="", ), ] AnimalModuleReporter.report_sold_animal_information(herd_statistics) - assert mock_om_add_variable.call_count == 11 * 7 + assert mock_om_add_variable.call_count == 11 * 8 def test_report_stillborn_calves_information(mocker: MockerFixture) -> None: @@ -1074,6 +1171,7 @@ def test_report_end_of_simulation_empty_sold_animal_info(mocker: MockerFixture) mock_time := MagicMock(auto_spec=RufasTime), {}, {}, + {}, ) mock_report_sold_animal_information.assert_called_once_with(herd_statistics) @@ -1166,6 +1264,7 @@ def test_report_end_of_simulation(mocker: MockerFixture) -> None: mock_time := MagicMock(auto_spec=RufasTime), {}, {}, + {}, ) mock_report_sold_animal_information.assert_called_once_with(herd_statistics) diff --git a/tests/test_biophysical/test_animal/test_data_types/test_milk_production.py b/tests/test_biophysical/test_animal/test_data_types/test_milk_production.py index ac75a38599..b10558f08d 100644 --- a/tests/test_biophysical/test_animal/test_data_types/test_milk_production.py +++ b/tests/test_biophysical/test_animal/test_data_types/test_milk_production.py @@ -1,4 +1,6 @@ from unittest.mock import MagicMock + +from RUFAS.biophysical.animal.data_types.animal_types import AnimalType from RUFAS.biophysical.animal.data_types.milk_production import ( MilkProductionInputs, MilkProductionOutputs, @@ -85,6 +87,20 @@ def test_statistics_initializes_with_given_values() -> None: milk_fat=1.3, milk_lactose=1.5, parity=2, + days_born=100, + days_in_pregnancy=100, + animal_type=AnimalType.LAC_COW, + TBV_fat=10.0, + TBV_protein=10.0, + E_permanent_fat=10.0, + E_temporary_fat=10.0, + E_permanent_protein=10.0, + E_temporary_protein=10.0, + phenotype_fat=10.0, + phenotype_protein=10.0, + EBV_fat=10.0, + EBV_protein=10.0, + ranking_index=10.0, ) assert stats.cow_id == 101 @@ -117,6 +133,20 @@ def test_statistics_milking_status_flag(days_in_milk: int, expected_flag: bool) milk_fat=1.2, milk_lactose=1.4, parity=1, + days_born=100, + days_in_pregnancy=100, + animal_type=AnimalType.LAC_COW, + TBV_fat=10.0, + TBV_protein=10.0, + E_permanent_fat=10.0, + E_temporary_fat=10.0, + E_permanent_protein=10.0, + E_temporary_protein=10.0, + phenotype_fat=10.0, + phenotype_protein=10.0, + EBV_fat=10.0, + EBV_protein=10.0, + ranking_index=10.0, ) assert stats.is_milking is expected_flag @@ -125,6 +155,17 @@ def test_statistics_milking_status_flag(days_in_milk: int, expected_flag: bool) def test_statistics_units_mapping_is_consistent() -> None: """UNITS dict should contain expected keys and associated measurement units.""" expected_units = { + "EBV_fat": MeasurementUnits.KILOGRAMS, + "EBV_protein": MeasurementUnits.KILOGRAMS, + "E_permanent_fat": MeasurementUnits.KILOGRAMS, + "E_permanent_protein": MeasurementUnits.KILOGRAMS, + "E_temporary_fat": MeasurementUnits.KILOGRAMS, + "E_temporary_protein": MeasurementUnits.KILOGRAMS, + "TBV_fat": MeasurementUnits.KILOGRAMS, + "TBV_protein": MeasurementUnits.KILOGRAMS, + "animal_type": MeasurementUnits.UNITLESS, + "days_born": MeasurementUnits.DAYS, + "days_in_pregnancy": MeasurementUnits.DAYS, "cow_id": MeasurementUnits.UNITLESS, "pen_id": MeasurementUnits.UNITLESS, "days_in_milk": MeasurementUnits.DAYS, @@ -133,6 +174,9 @@ def test_statistics_units_mapping_is_consistent() -> None: "milk_fat": MeasurementUnits.KILOGRAMS_PER_DAY, "milk_lactose": MeasurementUnits.KILOGRAMS_PER_DAY, "parity": MeasurementUnits.UNITLESS, + "phenotype_fat": MeasurementUnits.KILOGRAMS, + "phenotype_protein": MeasurementUnits.KILOGRAMS, + "ranking_index": MeasurementUnits.UNITLESS, "is_milking": MeasurementUnits.UNITLESS, "simulation_day": MeasurementUnits.SIMULATION_DAY, } diff --git a/tests/test_biophysical/test_animal/test_data_types/test_reproduction.py b/tests/test_biophysical/test_animal/test_data_types/test_reproduction.py index 4074d8272b..fd25408a60 100644 --- a/tests/test_biophysical/test_animal/test_data_types/test_reproduction.py +++ b/tests/test_biophysical/test_animal/test_data_types/test_reproduction.py @@ -40,7 +40,8 @@ def reproduction_inputs() -> ReproductionInputs: days_born=1000, days_in_pregnancy=150, days_in_milk=200, - net_merit=5.0, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, phosphorus_for_gestation_required_for_calf=0.8, ) @@ -127,7 +128,8 @@ def reproduction_data_stream( days_in_pregnancy=170, days_in_milk=230, events=sample_animal_events, - net_merit=6.5, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, phosphorus_for_gestation_required_for_calf=1.2, herd_reproduction_statistics=HerdReproductionStatistics(), newborn_calf_config=None, @@ -142,7 +144,8 @@ def test_reproduction_inputs_initialization(reproduction_inputs: ReproductionInp assert reproduction_inputs.days_born == 1000 assert reproduction_inputs.days_in_pregnancy == 150 assert reproduction_inputs.days_in_milk == 200 - assert reproduction_inputs.net_merit == 5.0 + assert reproduction_inputs.dam_tbv_fat == 10.0 + assert reproduction_inputs.dam_tbv_protein == 10.0 assert reproduction_inputs.phosphorus_for_gestation_required_for_calf == 0.8 @@ -359,7 +362,8 @@ def test_reproduction_data_stream_initialization(reproduction_data_stream: Repro assert reproduction_data_stream.days_in_pregnancy == 170 assert reproduction_data_stream.days_in_milk == 230 assert isinstance(reproduction_data_stream.events, AnimalEvents) - assert reproduction_data_stream.net_merit == 6.5 + assert reproduction_data_stream.dam_tbv_fat == 10.0 + assert reproduction_data_stream.dam_tbv_protein == 10.0 assert reproduction_data_stream.phosphorus_for_gestation_required_for_calf == 1.2 assert reproduction_data_stream.newborn_calf_config is None diff --git a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py index 0194cab1c5..8bb92cb346 100644 --- a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py +++ b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py @@ -7,6 +7,8 @@ from pytest_mock import MockerFixture from RUFAS.biophysical.animal.animal import Animal +from RUFAS.biophysical.animal.animal_config import AnimalConfig +from RUFAS.biophysical.animal.animal_genetics.animal_genetics import Genetics from RUFAS.biophysical.animal.bedding.bedding import Bedding from RUFAS.biophysical.animal.data_types.animal_enums import AnimalStatus, Breed from RUFAS.biophysical.animal.data_types.animal_events import AnimalEvents @@ -236,6 +238,7 @@ def test_perform_daily_routines_counts_deaths_and_handles_stillborn_newborns( """ dead_animal = MagicMock(spec=Animal) dead_animal.animal_type = AnimalType.LAC_COW + dead_animal.days_born = 888 dead_output = DailyRoutinesOutput( animal_status=AnimalStatus.DEAD, @@ -246,6 +249,9 @@ def test_perform_daily_routines_counts_deaths_and_handles_stillborn_newborns( calving_animal = MagicMock(spec=Animal) calving_animal.animal_type = AnimalType.DRY_COW + calving_animal.days_born = 576 + calving_animal.genetics = MagicMock(spec=Genetics) + mocker.patch.object(calving_animal.genetics, "recalculate_values_at_lactation_start") newborn_config: NewBornCalfValuesTypedDict = { "breed": Breed.HO.name, @@ -254,7 +260,6 @@ def test_perform_daily_routines_counts_deaths_and_handles_stillborn_newborns( "days_born": 0, "birth_weight": 10.1, "initial_phosphorus": 10.0, - "net_merit": 18.8, } calving_output = DailyRoutinesOutput( @@ -278,6 +283,7 @@ def test_perform_daily_routines_counts_deaths_and_handles_stillborn_newborns( mock_time = MagicMock(spec=RufasTime) mock_time.simulation_day = 0 + mock_time.current_date = datetime(2023, 1, 1) ( graduated_animals, @@ -487,7 +493,8 @@ def test_create_newborn_calf( days_born=0, birth_weight=10.1, initial_phosphorus=10.0, - net_merit=18.8, + dam_tbv_fat=10.0, + dam_tbv_protein=10.0, ) animal = mock_animal(animal_type=AnimalType.CALF, sold=is_newborn_calf_sold, stillborn=is_newborn_calf_stillborn) animal.events = MagicMock(auto_spec=AnimalEvents) @@ -495,11 +502,15 @@ def test_create_newborn_calf( mock_animal_init = mocker.patch("RUFAS.biophysical.animal.herd_manager.Animal", return_value=animal) - herd_manager._create_newborn_calf(newborn_calf_config, 0) + mock_time = MagicMock(auto_spec=RufasTime) + mock_time.simulation_day = 100 + mock_time.current_date = datetime.today() + + herd_manager._create_newborn_calf(newborn_calf_config, mock_time) expected_newborn_calf_config = newborn_calf_config.copy() expected_newborn_calf_config["id"] = AnimalPopulation.current_animal_id - mock_animal_init.assert_called_once_with(args=expected_newborn_calf_config, simulation_day=0) + mock_animal_init.assert_called_once_with(args=expected_newborn_calf_config, time=mock_time) if not (is_newborn_calf_stillborn or is_newborn_calf_sold): animal.events.add_event.assert_called_once() @@ -536,6 +547,7 @@ def test_check_if_heifers_need_to_be_sold( "cull_reason": "NA", "days_in_milk": "NA", "parity": "NA", + "genetic_history": str(removed_heiferIII.genetic_history), } for removed_heiferIII in expected_sold_heiferIIIs[:3] ] @@ -568,15 +580,11 @@ def test_check_if_replacement_heifers_needed( for replacement in herd_manager.replacement_market: replacement.days_born = 10 - mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics." - "assign_net_merit_value_to_animals_entering_herd", - return_vale=8.8, - ) - mock_time = MagicMock(auto_spec=RufasTime) mock_time.simulation_day = 100 mock_time.current_date = datetime.today() + AnimalConfig.average_phenotype["fat_kg"] = {mock_time.current_date.year: 10.0} + AnimalConfig.average_phenotype["protein_kg"] = {mock_time.current_date.year: 10.0} result = herd_manager._check_if_replacement_heifers_needed(mock_time) diff --git a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_herd_statistics.py b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_herd_statistics.py index 6bc3408498..e13cfe4978 100644 --- a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_herd_statistics.py +++ b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_herd_statistics.py @@ -57,7 +57,7 @@ def mock_cows_with_specific_parity(number_of_cows: int, parity: int) -> tuple[li if calving_to_pregnancy_time > 0 ] expected_average_calving_to_pregnancy_time = ( - sum(calving_to_pregnancy_times) / number_of_cows if number_of_cows > 0 else 0 + sum(calving_to_pregnancy_times) / len(calving_to_pregnancy_times) if len(calving_to_pregnancy_times) > 0 else 0 ) return cows, { @@ -598,6 +598,7 @@ def test_update_sold_and_died_cow_statistics( cull_reason=cow.cull_reason, days_in_milk=cow.days_in_milk, parity=cow.reproduction.calves, + genetic_history=str(cow.genetic_history), ) for cow in sold_and_died_cows ] @@ -647,6 +648,7 @@ def test_update_sold_and_died_cow_statistics( cull_reason=cow.cull_reason, days_in_milk=cow.days_in_milk, parity=cow.reproduction.calves, + genetic_history=str(cow.genetic_history), ) for cow in sold_cows ] @@ -721,6 +723,7 @@ def test_update_sold_heiferII_statistics( cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history=str(heiferII.genetic_history), ) for heiferII in sold_heiferIIs ] @@ -764,6 +767,7 @@ def test_update_sold_newborn_calf_statistics( cull_reason="NA", days_in_milk="NA", parity="NA", + genetic_history=str(calf.genetic_history), ) for calf in sold_calves ] diff --git a/tests/test_biophysical/test_animal/test_reproduction/test_reproduction.py b/tests/test_biophysical/test_animal/test_reproduction/test_reproduction.py index 3beaa93694..79322840ac 100644 --- a/tests/test_biophysical/test_animal/test_reproduction/test_reproduction.py +++ b/tests/test_biophysical/test_animal/test_reproduction/test_reproduction.py @@ -47,8 +47,9 @@ def mock_reproduction_inputs( days_born: int = 0, days_in_pregnancy: int = 0, days_in_milk: int = 0, - net_merit: float = 0.0, phosphorus_for_gestation_required_for_calf: float = 0.0, + dam_tbv_fat: float = 0.0, + dam_tbv_protein: float = 0.0, ) -> ReproductionInputs: return ReproductionInputs( animal_type=animal_type, @@ -57,8 +58,9 @@ def mock_reproduction_inputs( days_born=days_born, days_in_pregnancy=days_in_pregnancy, days_in_milk=days_in_milk, - net_merit=net_merit, phosphorus_for_gestation_required_for_calf=phosphorus_for_gestation_required_for_calf, + dam_tbv_fat=dam_tbv_fat, + dam_tbv_protein=dam_tbv_protein, ) @@ -88,7 +90,6 @@ def mock_reproduction_data_stream( days_born: int = 0, days_in_pregnancy: int = 0, days_in_milk: int = 0, - net_merit: float = 0.0, phosphorus_for_gestation_required_for_calf: float = 0.0, events: AnimalEvents = AnimalEvents(), newborn_calf_config: NewBornCalfValuesTypedDict | None = None, @@ -101,10 +102,11 @@ def mock_reproduction_data_stream( days_in_pregnancy=days_in_pregnancy, days_in_milk=days_in_milk, events=events, - net_merit=net_merit, phosphorus_for_gestation_required_for_calf=phosphorus_for_gestation_required_for_calf, herd_reproduction_statistics=HerdReproductionStatistics(), newborn_calf_config=newborn_calf_config, + dam_tbv_fat=0.0, + dam_tbv_protein=0.0, ) @@ -378,10 +380,11 @@ def test_reproduction_update(animal_type: AnimalType, mock_reproduction: Reprodu days_in_pregnancy=mock_inputs.days_in_pregnancy, days_in_milk=mock_inputs.days_in_milk, events=AnimalEvents(), - net_merit=mock_inputs.net_merit, phosphorus_for_gestation_required_for_calf=mock_inputs.phosphorus_for_gestation_required_for_calf, herd_reproduction_statistics=HerdReproductionStatistics(), newborn_calf_config=None, + dam_tbv_fat=0.0, + dam_tbv_protein=0.0, ) expected_outputs = mock_reproduction_outputs( @@ -434,10 +437,11 @@ def test_reproduction_update_type_error( days_in_pregnancy=mock_inputs.days_in_pregnancy, days_in_milk=mock_inputs.days_in_milk, events=AnimalEvents(), - net_merit=mock_inputs.net_merit, phosphorus_for_gestation_required_for_calf=mock_inputs.phosphorus_for_gestation_required_for_calf, herd_reproduction_statistics=HerdReproductionStatistics(), newborn_calf_config=None, + dam_tbv_fat=0.0, + dam_tbv_protein=0.0, ) mocker.patch( @@ -639,7 +643,6 @@ def test_cow_reproduction_update( days_born=0, birth_weight=10.8, initial_phosphorus=18.8, - net_merit=8.8, ), ) @@ -707,7 +710,6 @@ def test_cow_give_birth(calves: int, mocker: MockerFixture) -> None: days_in_pregnancy=reproduction.gestation_length, days_in_milk=150, phosphorus_for_gestation_required_for_calf=18.8, - net_merit=23.3, ) mock_reset_repro_state = mocker.patch.object(reproduction.repro_state_manager, "reset") @@ -720,9 +722,6 @@ def test_cow_give_birth(calves: int, mocker: MockerFixture) -> None: mock_simulate_estrus_if_eligible = mocker.patch.object( reproduction, "_simulate_estrus_if_eligible", return_value=mock_outputs ) - mock_net_merit_assignment = mocker.patch( - "RUFAS.biophysical.animal.animal_genetics.animal_genetics.AnimalGenetics.assign_net_merit_value_to_newborn_calf" - ) result = reproduction.cow_give_birth(mock_outputs, mock_time) @@ -738,7 +737,6 @@ def test_cow_give_birth(calves: int, mocker: MockerFixture) -> None: mock_outputs, mock_time.simulation_day ) mock_simulate_estrus_if_eligible.assert_called_once_with(mock_outputs, mock_time.simulation_day) - mock_net_merit_assignment.assert_called_once_with(mock_time, mock_outputs.breed, mock_outputs.net_merit) @pytest.mark.parametrize( diff --git a/tests/test_input_manager.py b/tests/test_input_manager.py index 7613221351..417300c8ad 100644 --- a/tests/test_input_manager.py +++ b/tests/test_input_manager.py @@ -603,7 +603,7 @@ def test_start_data_processing_invalid_properties_routes_logs_and_raises( "config", "animal", "animal_population", - "animal_net_merit", + "animal_mean_phenotype", "animal_top_listing_semen", "lactation", "economy", @@ -631,7 +631,7 @@ def test_start_data_processing_invalid_properties_routes_logs_and_raises( "config", "animal", "animal_population", - "animal_net_merit", + "animal_mean_phenotype", "animal_top_listing_semen", "lactation", "economy", @@ -658,7 +658,7 @@ def test_start_data_processing_invalid_properties_routes_logs_and_raises( "config", "animal", "animal_population", - "animal_net_merit", + "animal_mean_phenotype", "animal_top_listing_semen", "economy", "emission", diff --git a/tests/test_simulation_engine.py b/tests/test_simulation_engine.py index be84cf9ba9..a1317889ba 100644 --- a/tests/test_simulation_engine.py +++ b/tests/test_simulation_engine.py @@ -119,6 +119,7 @@ def test_simulate( simulation_engine.time, simulation_engine.herd_manager.heiferII_events_by_id, simulation_engine.herd_manager.cow_events_by_id, + simulation_engine.herd_manager.animal_genetic_history_by_id, ) mock_estimate_emissions.assert_called_once()