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

1""" 

2Mite Population Module for BeePop+ Varroa Mite Simulation 

3 

4This module models Varroa destructor mite populations for BeePop+ honey bee colony 

5simulation, tracking resistant and non-resistant subpopulations. 

6 

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. 

13 

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. 

16 

17Classes: 

18 Mite: Varroa mite population with resistant/non-resistant tracking 

19""" 

20 

21 

22class Mite: 

23 """ 

24 Varroa destructor mite population model for BeePop+ simulation. 

25 

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. 

31 

32 Note: 

33 Arithmetic operations include C++ compatibility features like integer 

34 truncation in addition operations to maintain exact simulation reproducibility. 

35 """ 

36 

37 def __init__(self, resistant=0.0, non_resistant=0.0): 

38 self.resistant = resistant 

39 self.non_resistant = non_resistant 

40 

41 def zero(self): 

42 self.resistant = 0.0 

43 self.non_resistant = 0.0 

44 

45 def get_resistant(self): 

46 return self.resistant 

47 

48 def get_non_resistant(self): 

49 return self.non_resistant 

50 

51 def set_resistant(self, num): 

52 self.resistant = num 

53 

54 def set_non_resistant(self, num): 

55 self.non_resistant = num 

56 

57 def get_total(self): 

58 return self.resistant + self.non_resistant 

59 

60 def get_pct_resistant(self): 

61 total = self.get_total() 

62 return (100.0 * self.resistant / total) if total > 0 else 0.0 

63 

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 

68 

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 

80 

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 

94 

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 

106 

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 

114 

115 def __mul__(self, value): 

116 if isinstance(value, (int, float)): 

117 return Mite(self.resistant * value, self.non_resistant * value) 

118 return NotImplemented 

119 

120 def __int__(self): 

121 return int(self.get_total()) 

122 

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 ) 

130 

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 

139 

140 def __str__(self): 

141 return f"Mite(resistant={self.resistant}, non_resistant={self.non_resistant})"