Coverage for pybeepop/beepop/mite.py: 63%
76 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-19 20:58 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-19 20:58 +0000
1"""
2Mite Population Module for BeePop+ Varroa Mite Simulation
4This module models Varroa destructor mite populations for BeePop+ honey bee colony
5simulation, tracking resistant and non-resistant subpopulations.
7Resistance to Varroa treatment is a property of the mite population, not of an individual
8treatment. Resistant mites enter the population two ways: InitMitePctResistant sets the
9proportion of the initial infestation, and PctImmMitesResistant sets the proportion of
10each batch of immigrating mites, which arrive with their own split rather than adopting
11the resident population's. Mites produced by reproduction scale the parent population's
12split, so offspring inherit the parents' resistant proportion.
14Treatments kill only the non-resistant subpopulation, so a schedule of repeated treatments
15selects for resistance: the resistant share rises as susceptible mites are removed.
17Classes:
18 Mite: Varroa mite population with resistant/non-resistant tracking
19"""
22class Mite:
23 """
24 Varroa destructor mite population model for BeePop+ simulation.
26 Attributes:
27 resistant (float): Number of treatment-resistant mites, which survive Varroa
28 treatments.
29 non_resistant (float): Number of treatment-susceptible mites, which die at the
30 treatment's pct_mortality while a treatment is active.
32 Note:
33 Arithmetic operations include C++ compatibility features like integer
34 truncation in addition operations to maintain exact simulation reproducibility.
35 """
37 def __init__(self, resistant=0.0, non_resistant=0.0):
38 self.resistant = resistant
39 self.non_resistant = non_resistant
41 def zero(self):
42 self.resistant = 0.0
43 self.non_resistant = 0.0
45 def get_resistant(self):
46 return self.resistant
48 def get_non_resistant(self):
49 return self.non_resistant
51 def set_resistant(self, num):
52 self.resistant = num
54 def set_non_resistant(self, num):
55 self.non_resistant = num
57 def get_total(self):
58 return self.resistant + self.non_resistant
60 def get_pct_resistant(self):
61 total = self.get_total()
62 return (100.0 * self.resistant / total) if total > 0 else 0.0
64 def set_pct_resistant(self, pct):
65 total = self.get_total()
66 self.resistant = total * pct / 100.0
67 self.non_resistant = total - self.resistant
69 def __iadd__(self, other):
70 if isinstance(other, Mite):
71 self.resistant += other.resistant
72 self.non_resistant += other.non_resistant
73 elif isinstance(other, (int, float)):
74 total = self.get_total()
75 pctres = self.resistant / total if total > 0 else 0.0
76 addtores = other * pctres
77 self.resistant += addtores
78 self.non_resistant += other - addtores
79 return self
81 def __isub__(self, other):
82 if isinstance(other, Mite):
83 self.resistant -= other.resistant
84 self.non_resistant -= other.non_resistant
85 elif isinstance(other, (int, float)):
86 total = self.get_total()
87 pctres = self.resistant * 100 / total if total > 0 else 0.0
88 subfromres = other * pctres / 100.0
89 self.resistant -= subfromres
90 self.non_resistant -= other - subfromres
91 self.resistant = max(0.0, self.resistant)
92 self.non_resistant = max(0.0, self.non_resistant)
93 return self
95 def __add__(self, other):
96 # Truncates to whole mites, matching C++ CMite::operator+(CMite). This differs
97 # from __iadd__ above, which does not truncate — also matching C++, where
98 # operator+= is defined separately. `a + b` and `a += b` are therefore NOT
99 # interchangeable here; swapping one for the other changes results.
100 # C++ defines no operator+(double), so scalars are unsupported.
101 if isinstance(other, Mite):
102 res = self.resistant + other.resistant
103 nres = self.non_resistant + other.non_resistant
104 return Mite(int(res), int(nres))
105 return NotImplemented
107 def __sub__(self, other):
108 # C++ defines no operator-(double), so scalars are unsupported.
109 if isinstance(other, Mite):
110 res = self.resistant - other.resistant
111 nres = self.non_resistant - other.non_resistant
112 return Mite(max(0.0, res), max(0.0, nres))
113 return NotImplemented
115 def __mul__(self, value):
116 if isinstance(value, (int, float)):
117 return Mite(self.resistant * value, self.non_resistant * value)
118 return NotImplemented
120 def __int__(self):
121 return int(self.get_total())
123 def __eq__(self, other):
124 if not isinstance(other, Mite):
125 return False
126 return (
127 self.resistant == other.resistant
128 and self.non_resistant == other.non_resistant
129 )
131 def assign_value(self, value):
132 """
133 Matches C++ CMite::operator=(double value) behavior.
134 Sets resistant = 0 and non_resistant = value.
135 """
136 self.resistant = 0.0
137 self.non_resistant = float(value)
138 return self
140 def __str__(self):
141 return f"Mite(resistant={self.resistant}, non_resistant={self.non_resistant})"