Coverage for pybeepop/beepop/queen.py: 77%

157 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-30 13:34 +0000

1"""BeePop+ Queen Bee Module. 

2 

3This module contains the Queen class that models the queen bee's behavior, 

4egg-laying dynamics, sperm reserves, and overall strength within a honey bee 

5colony simulation. The implementation is based on the BEEPOP model by 

6DeGrandi-Hoffman et al. (1988) and serves as a Python port of the C++ CQueen class. 

7 

8The Queen class manages the reproductive capacity of the colony, determining 

9daily egg production based on environmental factors, colony conditions, and 

10the queen's physiological state. 

11 

12Algorithm Basis: 

13 The egg-laying algorithm is derived from: 

14 "BEEPOP: A HONEYBEE POPULATION DYNAMICS SIMULATION MODEL" 

15 G. DeGrandi-Hoffman et al., 1988 

16 

17 The model incorporates multiple environmental and biological factors: 

18 - Queen strength and age (P factor) 

19 - Degree days/temperature (DD factor) 

20 - Daylight hours (L factor) 

21 - Forager population (N factor) 

22 - Nurse bee availability (larv_per_bee ratio) 

23 - Sperm reserves (affects drone egg proportion) 

24 

25Key Features: 

26 - Dynamic egg laying based on multiple environmental factors 

27 - Sperm depletion modeling for drone egg production 

28 - Queen strength system (1-5 scale) 

29 - Requeening event support with egg laying delays 

30 - Integration with colony resource and population dynamics 

31 

32""" 

33 

34from pybeepop.beepop.bee import Bee 

35import math 

36from pybeepop.beepop.globaloptions import GlobalOptions 

37 

38 

39class Queen(Bee): 

40 """Queen bee class for colony reproduction and egg-laying dynamics. 

41 

42 This class models the queen bee's reproductive behavior, including daily 

43 egg laying, sperm reserve management, and responses to environmental 

44 conditions. The queen's egg-laying capacity is determined by multiple 

45 factors including her strength, environmental conditions, and colony state. 

46 

47 The Queen class implements the BEEPOP egg-laying algorithm, which uses 

48 multiplicative factors to determine daily egg production: 

49 - P: Queen potential based on strength and age 

50 - DD: Degree-day (temperature) response 

51 - L: Daylight hours (photoperiod) response 

52 - N: Forager population response 

53 

54 The vars table defines queen capabilities for strength levels 1-5: 

55 - Strength 1: 1000 max eggs, 1.8M sperm 

56 - Strength 2: 1500 max eggs, 2.7M sperm 

57 - Strength 3: 2000 max eggs, 3.7M sperm 

58 - Strength 4: 2500 max eggs, 4.8M sperm 

59 - Strength 5: 3000 max eggs, 5.5M sperm 

60 

61 Note: 

62 Intermediate strength values are interpolated between table entries. 

63 Sperm depletion over time increases the proportion of drone eggs. 

64 High larv_per_bee ratios (>2.0) stop egg laying due to insufficient 

65 nurse bees. 

66 """ 

67 

68 def __init__(self): 

69 """Initialize a new Queen with default attributes. 

70 

71 Creates a queen bee with default strength (1.0), full sperm reserves, 

72 and empty egg counts. The queen starts with no egg-laying delay and 

73 is ready to begin laying eggs immediately upon simulation start. 

74 

75 The queen is initialized with the minimum strength level (1.0) and 

76 corresponding maximum egg laying capacity and sperm count from the 

77 lookup table. 

78 """ 

79 super().__init__(number=1) 

80 self.age = 0.0 

81 self.alive = True 

82 self.cum_weggs = 0 

83 self.cur_queen_day_1 = 1 

84 self.egg_laying_delay = 0 

85 self.weggs = 0 

86 self.deggs = 0 

87 self.teggs = 0 

88 self.max_eggs = 0.0 

89 self.DD = 0.0 

90 self.L = 0.0 

91 self.N = 0.0 

92 self.P = 0.0 

93 self.dd = 0.0 

94 self.l = 0.0 

95 self.n = 0.0 

96 self.current_sperm = 0.0 

97 self.initial_sperm = 5500000.0 

98 self.strength = 1.0 

99 # m_Vars: [max_eggs, initial_sperm] for strengths 1-5 

100 self.vars = [ 

101 [1000, 1800000], 

102 [1500, 2720000], 

103 [2000, 3650000], 

104 [2500, 4750000], 

105 [3000, 5500000], 

106 ] 

107 

108 # Only one version of each method below, preserving docstrings where present 

109 def get_DD(self): 

110 return self.DD 

111 

112 def get_L(self): 

113 return self.L 

114 

115 def get_N(self): 

116 return self.N 

117 

118 def get_P(self): 

119 return self.P 

120 

121 def get_dd(self): 

122 return self.dd 

123 

124 def get_l(self): 

125 return self.l 

126 

127 def get_n(self): 

128 return self.n 

129 

130 def get_weggs(self): 

131 return self.weggs 

132 

133 def get_deggs(self): 

134 return self.deggs 

135 

136 def get_teggs(self): 

137 return self.teggs 

138 

139 def set_initial_sperm(self, sperm): 

140 self.initial_sperm = sperm 

141 

142 def set_current_sperm(self, sperm): 

143 self.current_sperm = sperm 

144 

145 def get_initial_sperm(self): 

146 return self.initial_sperm 

147 

148 def get_current_sperm(self): 

149 return self.current_sperm 

150 

151 def set_strength(self, strength): 

152 """Set the queen's strength and update related attributes. 

153 

154 Sets the internal values of the Queen object based on the input strength 

155 by linearly interpolating between the lookup table values. The strength 

156 is constrained to the range [1.0, 5.0] and determines both maximum egg 

157 laying capacity and initial sperm count. 

158 

159 Args: 

160 strength (float): Queen strength rating between 1.0 and 5.0. 

161 Higher values indicate stronger queens with greater egg laying 

162 capacity and larger sperm reserves. 

163 

164 Note: 

165 Strength values are interpolated between discrete table entries. 

166 Values outside [1.0, 5.0] are clamped to the valid range. 

167 Setting strength also resets current sperm to initial sperm. 

168 """ 

169 self.strength = strength 

170 s = max(1.00000001, min(strength, 4.99999999)) 

171 i_strength = int(s) 

172 max_eggs1, sperm1 = self.vars[i_strength - 1] 

173 max_eggs2, sperm2 = self.vars[i_strength] 

174 self.max_eggs = max_eggs1 + (max_eggs2 - max_eggs1) * (s - i_strength) 

175 self.initial_sperm = sperm1 + (sperm2 - sperm1) * (s - i_strength) 

176 self.current_sperm = self.initial_sperm 

177 

178 def get_queen_strength(self): 

179 return self.strength 

180 

181 def get_strength(self): 

182 """Alias for get_queen_strength() to match expected interface.""" 

183 return self.strength 

184 

185 def set_max_eggs(self, max_eggs): 

186 self.max_eggs = max_eggs 

187 

188 def get_max_eggs(self): 

189 return self.max_eggs 

190 

191 def set_egg_laying_delay(self, delay): 

192 self.egg_laying_delay = delay 

193 

194 def set_day_one(self, day_num): 

195 self.cur_queen_day_1 = day_num 

196 

197 def requeen(self, egg_laying_delay, queen_strength, sim_day_num): 

198 self.egg_laying_delay = egg_laying_delay 

199 self.cur_queen_day_1 = sim_day_num 

200 self.set_strength(queen_strength) 

201 

202 def compute_L(self, daylight_hours): 

203 """ 

204 Computes the DaylightHours-based component for egg-laying. 

205 Uses the main algorithm from the original C++ code. 

206 Gets the threshold from GlobalOptions singleton. 

207 """ 

208 threshold = GlobalOptions.get().daylight_hours_threshold 

209 L = 0.0 

210 if daylight_hours > threshold: 

211 L = math.log10((daylight_hours + 0.3) * 0.1) * 7.889 

212 L = max(0, min(L, 1.0)) 

213 return L 

214 

215 def get_prop_drone_eggs(self): 

216 """ 

217 Calculates the proportion of drone eggs based on sperm reserves. 

218 Prevents divide by zero and uses a cubic polynomial fit from the original model. 

219 """ 

220 if self.initial_sperm == 0: 

221 return 0.0 

222 propsperm = (self.initial_sperm - self.current_sperm) / self.initial_sperm 

223 if propsperm < 0.6: 

224 pde = 0.0 

225 else: 

226 pde = 1 - ( 

227 -6.355 * propsperm**3 + 7.657 * propsperm**2 - 2.3 * propsperm + 1.002 

228 ) 

229 return max(0.0, pde) 

230 

231 def lay_eggs( 

232 self, lay_days, degree_days, daylight_hours, num_foragers, larv_per_bee 

233 ): 

234 """ 

235 Main egg-laying algorithm. Sets daily egg counts and updates sperm reserves. 

236 Implements logic from BEEPOP and the original C++ code. 

237 """ 

238 if larv_per_bee > 2: 

239 # Not enough House Bees 

240 self.weggs = 0 # Set egg count to 0 

241 self.deggs = 0 # Set egg count to 0 

242 self.teggs = 0 

243 self.DD = 0 

244 self.L = 0 

245 self.N = 0 

246 self.P = 0 

247 self.dd = 0 

248 self.l = 0 

249 self.n = 0 

250 return 

251 

252 # Pre-decrement egg laying delay to match C++ behavior: --m_EggLayingDelay > 0 

253 self.egg_laying_delay -= 1 

254 if self.egg_laying_delay > 0: 

255 # Still some egg laying delay from re-queening 

256 self.weggs = 0 # Set egg count to 0 

257 self.deggs = 0 # Set egg count to 0 

258 self.teggs = 0 

259 self.DD = 0 

260 self.L = 0 

261 self.N = 0 

262 self.P = 0 

263 self.dd = 0 

264 self.l = 0 

265 self.n = 0 

266 return 

267 lay_days = ( 

268 lay_days - self.cur_queen_day_1 

269 ) # Correct if there has been a re-queening 

270 P = self.max_eggs + -0.0027 * lay_days**2 + 0.395 * lay_days 

271 P = max(0, P) 

272 DD = -0.0006 * degree_days**2 + 0.05 * degree_days + 0.021 

273 DD = max(0, min(DD, 1)) 

274 if degree_days == 0: 

275 DD = 0 

276 L = self.compute_L(daylight_hours) 

277 N = math.log10((num_foragers * 0.001) + 1) * 0.672 

278 N = max(0, min(N, 1)) 

279 E = DD * L * N * P 

280 self.DD = DD 

281 self.L = L 

282 self.N = N 

283 self.P = P 

284 self.dd = degree_days 

285 self.l = daylight_hours 

286 self.n = num_foragers 

287 S = self.get_prop_drone_eggs() 

288 # Alternate L and F calculation (historical, not main algorithm) 

289 L_alt = math.log10(daylight_hours * 0.1) * 0.284 if daylight_hours > 0 else 0 

290 L_alt = max(0, L_alt) 

291 F = math.log10(num_foragers * 0.0006) * 0.797 if num_foragers > 0 else 0 

292 F = max(0, F) 

293 B = L_alt * F 

294 Z = S + B 

295 Z = min(Z, 1.0) 

296 self.deggs = int(E * Z) 

297 self.weggs = int(E - self.deggs) 

298 self.teggs = int(E) 

299 self.current_sperm -= 10 * self.weggs 

300 self.current_sperm = max(0, self.current_sperm) 

301 # Only 85% of eggs become adults 

302 self.deggs = int(self.deggs * 0.85) 

303 self.weggs = int(self.weggs * 0.85) 

304 self.teggs = int(self.teggs * 0.85) 

305 self.cum_weggs += self.weggs 

306 

307 def __str__(self): 

308 return f"Queen(strength={self.strength}, max_eggs={self.max_eggs}, initial_sperm={self.initial_sperm}, current_sperm={self.current_sperm}, weggs={self.weggs}, deggs={self.deggs}, teggs={self.teggs})"