Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e88fb45
Update changelog.md
allisterakun Feb 5, 2026
66ac5b1
Update changelog.md
allisterakun Feb 5, 2026
2ab48cd
Merge branch 'dev' into genetics_implementation
allisterakun Feb 5, 2026
b8536f4
Revert "Merge branch 'dev' into genetics_implementation"
allisterakun Feb 10, 2026
1f723ab
2721
allisterakun Feb 10, 2026
afd4baf
manure sync with dev
allisterakun Feb 10, 2026
f6b50a2
Merge branch 'dev' into genetics_implementation
allisterakun Feb 10, 2026
ef499f2
Merge f6b50a2751afe8b71e77c0b9ce6160bb6a96524c into 2f73b6a8e7abf5588…
allisterakun Feb 10, 2026
3e1c3e0
Apply Black Formatting
github-actions[bot] Feb 10, 2026
36e8c62
Update badges on README
allisterakun Feb 10, 2026
16486e5
Merge branch 'dev' into genetics_implementation
allisterakun Feb 11, 2026
fe775e1
Merge 16486e56232cf155ca13de7d596d44c91b35df7e into 6e54aeb1e8d42e1b9…
allisterakun Feb 11, 2026
9e20d2a
Apply Black Formatting
github-actions[bot] Feb 11, 2026
86fb626
Update badges on README
allisterakun Feb 11, 2026
e72c006
Update input_manager.py
allisterakun Mar 23, 2026
879fda1
Merge branch 'dev' into genetics_implementation
allisterakun Mar 23, 2026
7be9224
Merge 879fda17c62e388b2beed75b916961b3d2f6d4ba into 19a697be425755d8b…
allisterakun Mar 23, 2026
e67e054
Apply Black Formatting
github-actions[bot] Mar 23, 2026
64c0432
Update badges on README
allisterakun Mar 23, 2026
df3d25c
update all file paths to `TopListingSemen_HO.csv`
allisterakun Mar 25, 2026
a77ad58
Merge branch 'dev' into genetics_implementation
allisterakun Mar 25, 2026
2ae07a5
Merge a77ad5882b07b1854d3e6c5e93a7dbae5315a972 into 6b1bf25ddea38a70d…
allisterakun Mar 25, 2026
be8aa0d
Apply Black Formatting
github-actions[bot] Mar 25, 2026
957d1cd
fix broken unit tests
allisterakun Mar 25, 2026
8111ec8
Merge 957d1cdace856c0ac8895937ddf6c142bfdcde7c into 6b1bf25ddea38a70d…
allisterakun Mar 25, 2026
f0a104d
Apply Black Formatting
github-actions[bot] Mar 25, 2026
97cabc1
Update badges on README
allisterakun Mar 25, 2026
723fff6
mypy error fix part 1
allisterakun Mar 25, 2026
7e31b68
Merge 723fff696880856ffb51a03bef86686fd888cd6a into 6b1bf25ddea38a70d…
allisterakun Mar 25, 2026
9f7f834
Apply Black Formatting
github-actions[bot] Mar 25, 2026
0ff5420
Update badges on README
allisterakun Mar 25, 2026
c1f2b25
flake8
allisterakun Mar 25, 2026
f7e4df9
Merge c1f2b25fcb4037d438f528107b5bf66532090186 into 6b1bf25ddea38a70d…
allisterakun Mar 25, 2026
a1ec6da
Apply Black Formatting
github-actions[bot] Mar 25, 2026
c19bc07
Update badges on README
allisterakun Mar 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
118 changes: 103 additions & 15 deletions RUFAS/biophysical/animal/animal.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -151,7 +156,7 @@ def __init__(
| HeiferIIIValuesTypedDict
| CowValuesTypedDict
),
simulation_day: int = 0,
time: RufasTime,
) -> None:
"""
Initializes an Animal object.
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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__,
},
)
17 changes: 17 additions & 0 deletions RUFAS/biophysical/animal/animal_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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"
}
Loading