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
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-30 13:34 +0000
1"""BeePop+ Queen Bee Module.
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.
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.
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
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)
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
32"""
34from pybeepop.beepop.bee import Bee
35import math
36from pybeepop.beepop.globaloptions import GlobalOptions
39class Queen(Bee):
40 """Queen bee class for colony reproduction and egg-laying dynamics.
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.
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
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
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 """
68 def __init__(self):
69 """Initialize a new Queen with default attributes.
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.
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 ]
108 # Only one version of each method below, preserving docstrings where present
109 def get_DD(self):
110 return self.DD
112 def get_L(self):
113 return self.L
115 def get_N(self):
116 return self.N
118 def get_P(self):
119 return self.P
121 def get_dd(self):
122 return self.dd
124 def get_l(self):
125 return self.l
127 def get_n(self):
128 return self.n
130 def get_weggs(self):
131 return self.weggs
133 def get_deggs(self):
134 return self.deggs
136 def get_teggs(self):
137 return self.teggs
139 def set_initial_sperm(self, sperm):
140 self.initial_sperm = sperm
142 def set_current_sperm(self, sperm):
143 self.current_sperm = sperm
145 def get_initial_sperm(self):
146 return self.initial_sperm
148 def get_current_sperm(self):
149 return self.current_sperm
151 def set_strength(self, strength):
152 """Set the queen's strength and update related attributes.
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.
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.
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
178 def get_queen_strength(self):
179 return self.strength
181 def get_strength(self):
182 """Alias for get_queen_strength() to match expected interface."""
183 return self.strength
185 def set_max_eggs(self, max_eggs):
186 self.max_eggs = max_eggs
188 def get_max_eggs(self):
189 return self.max_eggs
191 def set_egg_laying_delay(self, delay):
192 self.egg_laying_delay = delay
194 def set_day_one(self, day_num):
195 self.cur_queen_day_1 = day_num
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)
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
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)
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
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
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})"