Source code for virtual_ecosystem.models.animal.animal_traits
"""The `models.animal.animal_traits` module contains classes that organizes
animal traits into enumerations for use by the Functional Group class in the
:mod:`~virtual_ecosystem.models.animal.functional_group` module.
""" # noqa: D205
from __future__ import annotations
from enum import Enum, Flag, auto
[docs]
class DietType(Flag):
"""Enumeration for diet resource types.
TODO: refine categorizations
"""
ALGAE = auto()
DETRITUS = auto()
FLOWERS = auto()
FOLIAGE = auto()
FRUIT = auto()
MUSHROOMS = auto()
FUNGI = auto()
SEEDS = auto()
BLOOD = auto()
INVERTEBRATES = auto()
NECTAR = auto()
FISH = auto()
CARCASSES = auto()
VERTEBRATES = auto()
WASTE = auto()
WOOD = auto()
NONFEEDING = auto()
POM = auto()
BACTERIA = auto()
HERBIVORE = (
ALGAE
| DETRITUS
| FLOWERS
| FOLIAGE
| FRUIT
| SEEDS
| NECTAR
| WOOD
| NONFEEDING # not strictly correct
)
CARNIVORE = BLOOD | INVERTEBRATES | FISH | VERTEBRATES | CARCASSES | WASTE
OMNIVORE = HERBIVORE | CARNIVORE
[docs]
@classmethod
def parse(cls, diet_string: str) -> DietType:
"""Parse a string of underscore-separated diet terms into a DietType flag.
This method takes a lowercase string such as 'fruit_foliage_fish' and converts
it into a combined DietType flag using bitwise OR logic. This allows diet
traits to be specified flexibly in configuration files or CSV inputs.
Args:
diet_string: A lowercase underscore-separated string representing one or
more diet components (e.g., 'foliage', 'fruit_fish', 'nectar_fungus').
Returns:
A DietType flag representing the combined diet traits.
"""
diet_string = diet_string.lower()
# Handle known composite categories directly
if diet_string == "herbivore":
return cls.HERBIVORE
elif diet_string == "carnivore":
return cls.CARNIVORE
elif diet_string == "omnivore":
return cls.OMNIVORE
# Otherwise parse individual components
parts = diet_string.split("_")
try:
flags = getattr(cls, parts[0].upper())
for part in parts[1:]:
flags |= getattr(cls, part.upper())
except AttributeError as e:
raise ValueError(f"Invalid diet term in string: {diet_string}") from e
return flags
[docs]
def coarse_category(self) -> DietType:
"""Classify the detailed diet into a broad trophic category.
This method examines the components of the current DietType flag and returns one
of the three broad trophic categories: HERBIVORE, CARNIVORE, or OMNIVORE. These
categories are defined as composite flags within the DietType enumeration.
- Returns OMNIVORE if the diet includes both plant/fungal and animal-derived
resources.
- Returns CARNIVORE if the diet includes only animal-derived resources.
- Returns HERBIVORE for all other combinations, including plant-only or empty
diets.
Returns:
DietType: A diet type flag representing the coarse category.
"""
is_herb = bool(self & DietType.HERBIVORE)
is_carn = bool(self & DietType.CARNIVORE)
if is_herb and is_carn:
return DietType.OMNIVORE
elif is_carn:
return DietType.CARNIVORE
else:
return DietType.HERBIVORE
[docs]
def count_dietary_categories(self) -> int:
"""Count the number of distinct dietary categories in this flag set.
Returns:
An integer of the number of different type types possessed by the functional
group.
"""
excluded = {"HERBIVORE", "CARNIVORE", "OMNIVORE", "NONFEEDING"}
return len(
[flag for flag in DietType if flag in self and flag.name not in excluded]
)
[docs]
class TaxaType(Enum):
"""Enumeration for taxa types."""
MAMMAL = "mammal"
BIRD = "bird"
INVERTEBRATE = "invertebrate"
AMPHIBIAN = "amphibian"
REPTILE = "reptile"
[docs]
class ReproductiveType(Enum):
"""Enumeration for reproductive types."""
SEMELPAROUS = "semelparous"
ITEROPAROUS = "iteroparous"
NONREPRODUCTIVE = "nonreproductive"
[docs]
class ReproductiveEnvironment(Enum):
"""Where and how reproduction happens: aquatic vs terrestrial."""
TERRESTRIAL = "terrestrial"
AQUATIC = "aquatic"
[docs]
class DevelopmentType(Enum):
"""Enumeration for development types."""
DIRECT = "direct"
INDIRECT = "indirect"
[docs]
class DevelopmentStatus(Enum):
"""Enumeration for development status."""
LARVAL = "larval"
ADULT = "adult"
[docs]
class ExcretionType(Enum):
"""Enumeration for excretion type."""
UREOTELIC = "ureotelic"
URICOTELIC = "uricotelic"
[docs]
class MigrationType(Enum):
"""Enumeration for external migration trait."""
NONE = "none"
SEASONAL = "seasonal"
[docs]
class VerticalOccupancy(Flag):
"""Enumeration for vertical occupancy trait."""
SOIL = auto()
GROUND = auto()
CANOPY = auto()
[docs]
@classmethod
def parse(cls, occupancy: str) -> VerticalOccupancy:
"""Convert a string like 'soil_ground' into a VerticalOccupancy flag.
This method parses a lowercase underscore-separated string into a combined
VerticalOccupancy flag using bitwise OR logic. It enables easy construction
of multi-layer occupancy traits from a single string field, such as those
found in CSV imports or config files.
Args:
occupancy: A string representing one or more vertical layers, such as
'soil', 'ground_canopy', or 'soil_ground_canopy'.
Returns:
A VerticalOccupancy flag representing the combined vertical occupancy.
"""
occupancy_list = occupancy.split("_")
occupancy_flags = getattr(cls, occupancy_list.pop(0).upper())
for oc in occupancy_list:
occupancy_flags = occupancy_flags | getattr(cls, oc.upper())
return occupancy_flags