Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
144 changes: 52 additions & 92 deletions RUFAS/biophysical/animal/animal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1767,9 +1767,6 @@ def daily_reproduction_update(
self.milk_production.set_wood_parameters(
wood_parameters["l"], wood_parameters["m"], wood_parameters["n"]
)
self.future_death_date = self.determine_future_death_date()
self._future_death_reason = animal_constants.DEATH_CULL
self.future_cull_date, self.cull_reason = self.determine_future_cull_date()

self.events += reproduction_outputs.events

Expand Down Expand Up @@ -2422,110 +2419,73 @@ def _get_cow_values(self) -> CowValuesTypedDict:
parity=self.calves,
)

def determine_future_death_date(self) -> int:
def assess_removal_risk(self, percent_fresh: float, time: RufasTime) -> None:
"""
Determine the future death date of the animal based on its parity.
Roll a cow's daily mortality and acute-sale risk and schedule any resulting removal.

Returns
-------
int
Calculated future death date in simulation days.
Parameters
----------
percent_fresh : float
Fraction of the herd currently fresh, used to scale the daily death and
acute-sale selection probabilities.
time : RufasTime
Current simulation time, used to record the day of death or sale.

Notes
Returns
-------
[AN.ANM.1]

None
"""
if self.calves >= 4:
death_rate = AnimalConfig.parity_death_probability[3]
else:
death_rate = AnimalConfig.parity_death_probability[self.calves - 1]
death_rand = random()
if death_rand <= death_rate:
death_probability_upper_limit = death_probability_lower_limit = 0.0
death_time_upper_limit = death_time_lower_limit = 0.0
death_date_random = random()
for i in range(len(AnimalConfig.death_day_probability) - 1):
if (
AnimalConfig.death_day_probability[i]
<= death_date_random
< AnimalConfig.death_day_probability[i + 1]
):
death_probability_lower_limit = AnimalConfig.death_day_probability[i]
death_probability_upper_limit = AnimalConfig.death_day_probability[i + 1]
death_time_lower_limit = AnimalConfig.cull_day_count[i]
death_time_upper_limit = AnimalConfig.cull_day_count[i + 1]
n = (death_time_upper_limit - death_time_lower_limit) / (
death_probability_upper_limit - death_probability_lower_limit
)
return round(
death_time_lower_limit + n * (death_date_random - death_probability_lower_limit) + self.days_born
)
return sys.maxsize
if self.future_cull_date == sys.maxsize:
if self.is_selected_for_acute_sale(percent_fresh):
self.future_cull_date = self.days_born
self.cull_reason = animal_constants.ACUTE_SALE_CULL
self.sold_at_day = time.simulation_day
return
if self.future_death_date == sys.maxsize:
if self.is_selected_for_death(percent_fresh):
self.future_death_date = self.days_born
self.cull_reason = self._future_death_reason = animal_constants.DEATH_CULL
self.dead_at_day = time.simulation_day

def _parity_index(self) -> int:
"""Return the 0-based index into a by-parity array, capping parity 4+ at the last entry."""
return 3 if self.calves >= 4 else self.calves - 1

def determine_future_cull_date(self) -> tuple[int, str]:
def is_selected_for_death(self, percent_fresh: float) -> bool:
"""
Determine the future cull date and reason for the animal based on parity-specific probabilities.
Roll the cow's daily mortality risk.

Returns
-------
tuple[int, str]
- Future cull date in simulation days.
- Reason for culling.
bool
``True`` if the cow is selected to die, ``False`` otherwise.
"""
average_daily_death_rate = AnimalConfig.parity_death_probability[self._parity_index()] / 365
percent_other = 1 - percent_fresh

Notes
-------
[AN.ANM.2]
daily_death_risk_other = average_daily_death_rate / (2.55 * percent_fresh + percent_other)
daily_death_risk_fresh = 2.55 * daily_death_risk_other

daily_death_risk = daily_death_risk_fresh if self.days_in_milk < 50 else daily_death_risk_other
return random() <= daily_death_risk

def is_selected_for_acute_sale(self, percent_fresh: float) -> bool:
"""
cull_reason = ""
future_cull_date = sys.maxsize
if self.calves >= 4:
inv_cull_rate = AnimalConfig.parity_cull_probability[3]
else:
inv_cull_rate = AnimalConfig.parity_cull_probability[self.calves - 1]
cull_rand = random()
if cull_rand <= inv_cull_rate:
cull_reason_rand = random()
cull_prob = 0.0
if cull_reason_rand <= (cull_prob := cull_prob + AnimalConfig.feet_leg_cull_probability):
cull_reason_cull_prob = AnimalConfig.feet_leg_cull_day_probability
cull_reason = animal_constants.LAMENESS_CULL

elif cull_reason_rand <= (cull_prob := cull_prob + AnimalConfig.injury_cull_probability):
cull_reason_cull_prob = AnimalConfig.injury_cull_day_probability
cull_reason = animal_constants.INJURY_CULL

elif cull_reason_rand <= (cull_prob := cull_prob + AnimalConfig.mastitis_cull_probability):
cull_reason_cull_prob = AnimalConfig.mastitis_cull_day_probability
cull_reason = animal_constants.MASTITIS_CULL

elif cull_reason_rand <= (cull_prob := cull_prob + AnimalConfig.disease_cull_probability):
cull_reason_cull_prob = AnimalConfig.disease_cull_day_probability
cull_reason = animal_constants.DISEASE_CULL

elif cull_reason_rand <= (cull_prob + AnimalConfig.udder_cull_probability):
cull_reason_cull_prob = AnimalConfig.udder_cull_day_probability
cull_reason = animal_constants.UDDER_CULL
Roll the cow's daily acute-sale (forced / involuntary) risk.

else:
cull_reason_cull_prob = AnimalConfig.unknown_cull_day_probability
cull_reason = animal_constants.UNKNOWN_CULL

cull_time_rand = random()
cull_reason_upper_limit = cull_reason_lower_limit = cull_time_upper_limit = cull_time_lower_limit = 0.0
for i in range(len(cull_reason_cull_prob) - 1):
if cull_reason_cull_prob[i] <= cull_time_rand < cull_reason_cull_prob[i + 1]:
cull_reason_lower_limit = cull_reason_cull_prob[i]
cull_reason_upper_limit = cull_reason_cull_prob[i + 1]
cull_time_lower_limit = AnimalConfig.cull_day_count[i]
cull_time_upper_limit = AnimalConfig.cull_day_count[i + 1]
x = (cull_time_upper_limit - cull_time_lower_limit) / (cull_reason_upper_limit - cull_reason_lower_limit)
future_cull_date = round(
cull_time_lower_limit + x * (cull_time_rand - cull_reason_lower_limit) + self.days_born
)
Returns
-------
bool
``True`` if the cow is selected for an acute sale, ``False`` otherwise.
"""
average_daily_removal_rate = AnimalConfig.parity_acute_sale_probability[self._parity_index()] / 365
percent_other = 1 - percent_fresh

daily_removal_risk_other = average_daily_removal_rate / (2.55 * percent_fresh + percent_other)
daily_removal_risk_fresh = 2.55 * daily_removal_risk_other

return future_cull_date, cull_reason
daily_removal_risk = daily_removal_risk_fresh if self.days_in_milk < 50 else daily_removal_risk_other
return random() <= daily_removal_risk

def update_pen_history(self, current_pen: int, current_day: int, animal_types_in_pen: set[AnimalType]) -> None:
"""
Expand Down
169 changes: 6 additions & 163 deletions RUFAS/biophysical/animal/animal_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,37 +146,10 @@ class AnimalConfig:
third_pregnancy_check_loss_rate : float
Pregnancy loss probability during the third pregnancy check, (unitless).
parity_death_probability : list[float]
List of probabilities of death based on parity number, (unitless).
death_day_probability : list[float]
Cumulative probability of cow death as a function of days in production, (unitless).
parity_cull_probability : list[float]
List of culling probabilities based on parity number, (unitless).
cull_day_count : list[int]
List of day intervals for culling analysis, (simulation day).
feet_leg_cull_probability : float
Probability of feet and leg-related culling, (unitless).
feet_leg_cull_day_probability : list[float]
Feet and leg-related culling probability over time, (unitless).
injury_cull_probability : float
Probability of culling due to injuries, (unitless).
injury_cull_day_probability : list[float]
Cumulative distribution for injury-related culling over time, (unitless).
mastitis_cull_probability : float
Probability of culling due to mastitis, (unitless).
mastitis_cull_day_probability : list[float]
Cumulative distribution for mastitis-related culling over time, (unitless).
disease_cull_probability : float
Probability of culling due to diseases, (unitless).
disease_cull_day_probability : list[float]
Cumulative distribution for disease-related culling over time, (unitless).
udder_cull_probability : float
Probability of culling due to udder-related issues, (unitless).
udder_cull_day_probability : list[float]
Cumulative distribution for udder-related culling over time, (unitless).
unknown_cull_probability : float
Probability of culling for unknown reasons, (unitless).
unknown_cull_day_probability : list[float]
Cumulative distribution for unknown reasons of culling over time, (unitless).
Annual, parity-indexed probability that a cow dies during a given year, (unitless).
parity_acute_sale_probability : list[float]
Annual, parity-indexed probability that a cow is sold for an acute / involuntary
reason during a given year, (unitless).
methane_mitigation_method : str
The mitigation method applied for methane reduction, e.g., "None", (unitless).
methane_mitigation_additive_amount : float
Expand Down Expand Up @@ -268,112 +241,7 @@ class AnimalConfig:
third_pregnancy_check_loss_rate: float = 0.017

parity_death_probability: list[float] = [0.039, 0.056, 0.085, 0.117]
death_day_probability: list[float] = [0, 0.18, 0.32, 0.42, 0.48, 0.54, 0.60, 0.65, 0.70, 0.77, 0.83, 0.89, 0.95, 1]

parity_cull_probability: list[float] = [0.169, 0.233, 0.301, 0.408]
cull_day_count: list[int] = [0, 5, 15, 45, 90, 135, 180, 225, 270, 330, 380, 430, 480, 530]
feet_leg_cull_probability: float = 0.1633
feet_leg_cull_day_probability: list[float] = [
0,
0.03,
0.08,
0.16,
0.25,
0.36,
0.48,
0.59,
0.69,
0.78,
0.85,
0.90,
0.95,
1,
]
injury_cull_probability: float = 0.2883
injury_cull_day_probability: list[float] = [
0,
0.08,
0.18,
0.28,
0.38,
0.47,
0.56,
0.64,
0.71,
0.78,
0.85,
0.90,
0.95,
1,
]
mastitis_cull_probability: float = 0.2439
mastitis_cull_day_probability: list[float] = [
0,
0.06,
0.12,
0.19,
0.30,
0.43,
0.56,
0.68,
0.78,
0.85,
0.90,
0.94,
0.97,
1,
]
disease_cull_probability: float = 0.1391
disease_cull_day_probability: list[float] = [
0,
0.04,
0.12,
0.24,
0.34,
0.42,
0.50,
0.57,
0.64,
0.72,
0.81,
0.89,
0.95,
1,
]
udder_cull_probability: float = 0.0645
udder_cull_day_probability: list[float] = [
0,
0.12,
0.24,
0.33,
0.41,
0.48,
0.55,
0.62,
0.68,
0.76,
0.82,
0.89,
0.95,
1,
]
unknown_cull_probability: float = 0.1009
unknown_cull_day_probability: list[float] = [
0,
0.05,
0.11,
0.18,
0.27,
0.37,
0.45,
0.54,
0.62,
0.70,
0.77,
0.84,
0.92,
1,
]
parity_acute_sale_probability: list[float] = [0.169, 0.233, 0.301, 0.408]

methane_model: dict[str, Any] = {
"calves": "Pattanaik",
Expand Down Expand Up @@ -515,32 +383,7 @@ def initialize_animal_config(cls) -> None:
cls.third_pregnancy_check_loss_rate = animal_config_data["from_literature"]["repro"]["preg_loss_rate_3"]

cls.parity_death_probability = animal_config_data["from_literature"]["culling"]["parity_death_prob"]
cls.death_day_probability = animal_config_data["from_literature"]["culling"]["death_day_prob"]

cls.parity_cull_probability = animal_config_data["from_literature"]["culling"]["parity_cull_prob"]
cls.cull_day_count = animal_config_data["from_literature"]["culling"]["cull_day_count"]
cls.feet_leg_cull_probability = animal_config_data["from_literature"]["culling"]["feet_leg_cull"]["probability"]
cls.feet_leg_cull_day_probability = animal_config_data["from_literature"]["culling"]["feet_leg_cull"][
"cull_day_prob"
]
cls.injury_cull_probability = animal_config_data["from_literature"]["culling"]["injury_cull"]["probability"]
cls.injury_cull_day_probability = animal_config_data["from_literature"]["culling"]["injury_cull"][
"cull_day_prob"
]
cls.mastitis_cull_probability = animal_config_data["from_literature"]["culling"]["mastitis_cull"]["probability"]
cls.mastitis_cull_day_probability = animal_config_data["from_literature"]["culling"]["mastitis_cull"][
"cull_day_prob"
]
cls.disease_cull_probability = animal_config_data["from_literature"]["culling"]["disease_cull"]["probability"]
cls.disease_cull_day_probability = animal_config_data["from_literature"]["culling"]["disease_cull"][
"cull_day_prob"
]
cls.udder_cull_probability = animal_config_data["from_literature"]["culling"]["udder_cull"]["probability"]
cls.udder_cull_day_probability = animal_config_data["from_literature"]["culling"]["udder_cull"]["cull_day_prob"]
cls.unknown_cull_probability = animal_config_data["from_literature"]["culling"]["unknown_cull"]["probability"]
cls.unknown_cull_day_probability = animal_config_data["from_literature"]["culling"]["unknown_cull"][
"cull_day_prob"
]
cls.parity_acute_sale_probability = animal_config_data["from_literature"]["culling"]["parity_acute_sale_prob"]

cls.methane_model = animal_data["methane_model"]
methane_mitigation_data = animal_data["methane_mitigation"]
Expand Down
7 changes: 1 addition & 6 deletions RUFAS/biophysical/animal/animal_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,7 @@
HEIFER_REPRO_CULL = "culled for heifer reproductive problem"
OVERSUPPLY_CULL = "culled for herd resize"
DEATH_CULL = "culled for death"
LAMENESS_CULL = "culled for lameness"
INJURY_CULL = "culled for injury"
MASTITIS_CULL = "culled for mastitis"
DISEASE_CULL = "culled for disease"
UDDER_CULL = "culled for udder"
UNKNOWN_CULL = "culled for unknown"
ACUTE_SALE_CULL = "culled for acute sale"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should we put for the acute sale reason?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope! We don't need that this time. Thanks for confirming!


# youngstock mortality (a loss from death, not a cull)
CALF_MORTALITY_LOSS = "died from pre-wean mortality"
Expand Down
7 changes: 1 addition & 6 deletions RUFAS/biophysical/animal/animal_module_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,12 +842,7 @@ def report_herd_statistics_data(cls, herd_statistics: HerdStatistics, simulation
cull_reason_stats_units = {
animal_constants.DEATH_CULL: MeasurementUnits.UNITLESS,
animal_constants.OVERSUPPLY_CULL: MeasurementUnits.UNITLESS,
animal_constants.LAMENESS_CULL: MeasurementUnits.UNITLESS,
animal_constants.INJURY_CULL: MeasurementUnits.UNITLESS,
animal_constants.MASTITIS_CULL: MeasurementUnits.UNITLESS,
animal_constants.DISEASE_CULL: MeasurementUnits.UNITLESS,
animal_constants.UDDER_CULL: MeasurementUnits.UNITLESS,
animal_constants.UNKNOWN_CULL: MeasurementUnits.UNITLESS,
animal_constants.ACUTE_SALE_CULL: MeasurementUnits.UNITLESS,
}
om.add_variable(
"cull_reason_stats",
Expand Down
Loading