Coverage for pybeepop/beepop/session.py: 47%
1276 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+ Session Management Module.
3This module contains the VarroaPopSession class, which serves as the central
4coordinator for BeePop+ simulations. It manages the simulation state, coordinates
5major objects (colony, weather, treatments), handles results output, and provides
6error/information reporting capabilities.
8The VarroaPopSession class is a Python port of the C++ CVarroaPopSession class
9and maintains compatibility with the original API while adding Python-specific
10enhancements.
12Architecture:
13 VarroaPopSession acts as the main controller that coordinates:
15 VarroaPopSession (Central Controller)
16 ├── Colony (Bee population dynamics)
17 ├── WeatherEvents (Environmental conditions)
18 ├── Results Management
19 │ ├── Output formatting
20 │ ├── Header generation
21 │ └── Data collection
22 ├── Treatment Protocols
23 │ ├── Mite treatments
24 │ ├── Spore treatments
25 │ └── Comb removal
26 ├── Immigration Events
27 │ ├── Mite immigration
28 │ └── Schedule management
29 ├── Requeening Events
30 │ ├── Queen replacement
31 │ └── Colony recovery
32 └── Error/Information Reporting
33 ├── Error tracking
34 ├── Warning management
35 └── Status reporting
37Key Features:
38 - Centralized simulation state management
39 - Results formatting and output coordination
40 - Weather event integration
41 - Treatment protocol scheduling
42 - Error and warning tracking
43 - Immigration and requeening event management
45"""
47import math
48from datetime import timedelta
49import datetime
50from pybeepop.beepop.colony import Colony
51from pybeepop.beepop.weatherevents import WeatherEvents
52from pybeepop.beepop.mite import Mite
53from pybeepop.beepop.mitetreatments import MiteTreatmentItem
56class VarroaPopSession:
57 """Central session manager for BeePop+ simulations.
59 This class serves as the main coordinator for BeePop+ simulations, managing
60 the simulation state, coordinating major simulation objects, handling results
61 output, and providing comprehensive error and information reporting.
63 The VarroaPopSession maintains the simulation timeline, manages colony and
64 weather interactions, coordinates treatment protocols, and formats output
65 data for analysis. It serves as a Python port of the C++ CVarroaPopSession.
67 """
69 def set_default_headers(self):
70 """
71 Sets the default header strings for output/plotting, matching the C++ results header logic.
72 """
73 self.results_header = [
74 "Date",
75 "ColSze",
76 "AdDrns",
77 "AdWkrs",
78 "Forgr",
79 "DrnBrd",
80 "WkrBrd",
81 "DrnLrv",
82 "WkrLrv",
83 "DrnEggs",
84 "WkrEggs",
85 "TotalEggs",
86 "DD",
87 "L",
88 "N",
89 "P",
90 "dd",
91 "l",
92 "n",
93 "FreeMts",
94 "DBrdMts",
95 "WBrdMts",
96 "Mts/DBrd",
97 "Mts/WBrd",
98 "Mts Dying",
99 "PropMts Dying",
100 "ColPollen(g)",
101 "PPestConc(ug/g)",
102 "ColNectar(g)",
103 "NPestConc(ug/g)",
104 "Dead DLarv",
105 "Dead WLarv",
106 "Dead DAdults",
107 "Dead WAdults",
108 "Dead Foragers",
109 "Queen Strength",
110 "Temp (DegC)",
111 "Precip",
112 "Min Temp (C)",
113 "Max Temp (C)",
114 "Daylight hours",
115 "Forage Inc",
116 "Forage Day",
117 ]
118 self.results_file_header = list(self.results_header)
119 # Add additional headers if needed for file output
121 def __init__(self):
122 """Initialize a new VarroaPopSession.
124 Creates a new simulation session with default settings, initializes
125 the colony and weather objects, sets up results tracking, and
126 configures all treatment and event management systems.
128 The session is initialized with conservative default values for all
129 parameters and empty data structures for results collection.
131 Raises:
132 RuntimeError: If colony or weather initialization fails.
134 Note:
135 After initialization, the session requires additional configuration
136 (dates, latitude, initial conditions) before running simulations.
137 """
138 # Output/Plotting Attributes
139 self.results_header = []
140 self.results_file_header = []
141 self.results_text = []
142 self.results_file_text = []
143 self.disp_weekly_data = False
144 # Options Selection
145 self.col_titles = True
146 self.init_conds = False
147 self.version = True
148 self.weather_colony = False
149 self.field_delimiter = 0
150 self.disp_frequency = 1
151 # Error/Status
152 self.error_list = []
153 self.information_list = []
154 self._enable_error_reporting = True
155 self._enable_info_reporting = True
156 # Simulation Data
157 self.sim_start_time = None
158 self.sim_end_time = None
159 self.simulation_complete = False
160 self.results_ready = False
161 # Immigration Data
162 self.immigration_type = "None"
163 self.tot_immigrating_mites = 0
164 self.inc_immigrating_mites = 0
165 self.cum_immigrating_mites = 0
166 self.imm_mite_pct_resistant = 0.0
167 # Use sentinel date (1999-01-01) to match C++ behavior when dates are not set
168 self.immigration_start_date = datetime.datetime(1999, 1, 1)
169 self.immigration_end_date = datetime.datetime(1999, 1, 1)
170 self.immigration_enabled = False
171 self.imm_enabled = False # ImmEnabled parameter
172 # Re-Queening Data
173 self.rq_egg_laying_delay = 10 # RQEggLayDelay parameter
174 self.rq_wkr_drn_ratio = 0.0
175 self.rq_enable_requeen = False
176 self.rq_scheduled = 1
177 self.rq_queen_strength = 5.0
178 self.rq_once = 0
179 self.rq_requeen_date = None
180 # Varroa Miticide Treatment Data
181 self.vt_treatment_start = None
182 self.vt_treatment_duration = 0
183 self.vt_mortality = 0
184 self.init_mite_pct_resistant = 0.0
185 self.vt_enable = False
186 # Varroa Spore Treatment Data
187 self.sp_enable = False
188 self.sp_treatment_start = None
189 self.sp_initial = 0
190 self.mort10 = 0.0
191 self.mort25 = 0.0
192 self.mort50 = 0.0
193 self.mort75 = 0.0
194 self.mort90 = 0.0
195 # Comb Removal
196 self.comb_remove_date = None
197 self.comb_remove_enable = False
198 self.comb_remove_pct = 0.0
199 # EPA Mortality
200 self.ied_enable = False
201 self.results_file_format_stg = ""
203 # Initialize main objects (matching C++ session constructor)
204 # Create the colony object (equivalent to CColony theColony)
205 self.colony = Colony(session=self)
207 # Set session reference in colony (matching theColony.SetSession(this))
208 if hasattr(self.colony, "set_session"):
209 self.colony.set_session(self)
211 # Create the weather events object (equivalent to new CWeatherEvents)
212 self.weather = WeatherEvents()
213 self.first_result_entry = True
214 self.weather_loaded = False
215 self.show_warnings = (
216 False # Not appropriate to show warnings for library version
217 )
219 # Main object accessors (matching C++ GetColony() and GetWeather())
220 def get_colony(self):
221 """Get the colony object (equivalent to C++ GetColony())."""
222 return self.colony
224 def get_weather(self):
225 """Get the weather events object (equivalent to C++ GetWeather())."""
226 return self.weather
228 def set_latitude(self, lat):
229 """Set the latitude for weather calculations."""
230 if self.weather is not None:
231 if hasattr(self.weather, "set_latitude"):
232 self.weather.set_latitude(lat)
234 def get_latitude(self):
235 """Get the current latitude setting."""
236 if self.weather is not None and hasattr(self.weather, "get_latitude"):
237 return self.weather.get_latitude()
238 return 30.0 # Default latitude
240 def is_weather_loaded(self):
241 """Check if weather data has been loaded."""
242 return self.weather_loaded
244 def set_weather_loaded(self, load_complete):
245 """Set the weather loaded status."""
246 self.weather_loaded = load_complete
248 def is_show_warnings(self):
249 """Check if warnings should be shown."""
250 return self.show_warnings
252 def set_show_warnings(self, warn):
253 """Set whether warnings should be shown."""
254 self.show_warnings = warn
256 # Error/Status list access
257 def clear_error_list(self):
258 self.error_list.clear()
260 def clear_info_list(self):
261 self.information_list.clear()
263 def add_to_error_list(self, err_stg):
264 self.error_list.append(err_stg)
266 def add_to_info_list(self, info_stg):
267 self.information_list.append(info_stg)
269 def get_error_list(self):
270 return self.error_list
272 def get_info_list(self):
273 return self.information_list
275 def is_error_reporting_enabled(self):
276 return self._enable_error_reporting
278 def is_info_reporting_enabled(self):
279 return self._enable_info_reporting
281 def enable_error_reporting(self, enable):
282 self._enable_error_reporting = bool(enable)
284 def enable_info_reporting(self, enable):
285 self._enable_info_reporting = bool(enable)
287 # Simulation Operations
288 def get_sim_start(self):
289 return self.sim_start_time
291 def get_sim_end(self):
292 return self.sim_end_time
294 def set_sim_start(self, start):
295 self.sim_start_time = start
296 # Mark that simulation dates were explicitly set
297 self._sim_dates_explicitly_set = True
299 def set_sim_end(self, end):
300 self.sim_end_time = end
301 # Mark that simulation dates were explicitly set
302 self._sim_dates_explicitly_set = True
304 def get_sim_days(self):
305 if self.sim_start_time and self.sim_end_time:
306 return (self.sim_end_time - self.sim_start_time).days + 1
307 return 0
309 def get_sim_day_number(self, the_date):
310 if self.sim_start_time:
311 return (the_date - self.sim_start_time).days + 1
312 return 0
314 def get_sim_date(self, day_num):
315 if self.sim_start_time:
316 return self.sim_start_time + timedelta(days=day_num)
317 return None
319 def ready_to_simulate(self):
320 return (
321 self.colony
322 and self.colony.is_initialized()
323 and self.weather
324 and self.weather.is_initialized()
325 )
327 def is_simulation_complete(self):
328 return self.simulation_complete
330 def are_results_ready(self):
331 return self.results_ready
333 def get_results_length(self):
334 return len(self.results_text)
336 def date_in_range(self, start_range, stop_range, the_time):
337 """
338 Helper function to check if a date falls within a range.
339 Matches C++ DateInRange function.
340 """
341 return (the_time >= start_range) and (the_time <= stop_range)
343 def check_date_consistency(self, show_warning=True):
344 """
345 Checks Dates for Immigration, Varroa Treatment and Re-Queening to verify
346 they fall inside the Simulation range. If not, a warning message is displayed
347 and the user is given the opportunity to continue or quit the simulation.
349 Return value: True if simulation should continue, False otherwise. User can
350 override consistency check and continue from warning message box. Otherwise,
351 inconsistent times will return false.
353 Ported from C++ CheckDateConsistency function.
354 """
355 consistent = True
356 if show_warning: # we only check if show_warnings is on. Is this intended?
357 warn_strings = []
359 # Check Re-Queening dates if enabled
360 if (
361 hasattr(self, "rq_enable_requeen")
362 and self.rq_enable_requeen
363 and hasattr(self, "rq_requeen_date")
364 and self.rq_requeen_date
365 ):
366 if not self.date_in_range(
367 self.sim_start_time, self.sim_end_time, self.rq_requeen_date
368 ):
369 warn_strings.append(" ReQueening")
370 consistent = False
372 # Check Immigration dates if enabled
373 if self.immigration_enabled:
374 # Check immigration start date
375 if self.immigration_start_date and not self.date_in_range(
376 self.sim_start_time, self.sim_end_time, self.immigration_start_date
377 ):
378 warn_strings.append(" Immigration Start")
379 consistent = False
381 # Check immigration end date
382 if self.immigration_end_date and not self.date_in_range(
383 self.sim_start_time, self.sim_end_time, self.immigration_end_date
384 ):
385 warn_strings.append(" Immigration End")
386 consistent = False
388 # Check Varroa Treatment dates if enabled
389 if (
390 hasattr(self, "vt_enable")
391 and self.vt_enable
392 and hasattr(self, "vt_treatment_start")
393 and self.vt_treatment_start
394 ):
395 if not self.date_in_range(
396 self.sim_start_time, self.sim_end_time, self.vt_treatment_start
397 ):
398 warn_strings.append(" Varroa Treatment Start")
399 consistent = False
401 # Display warnings if enabled and inconsistencies found
402 if show_warning and not consistent:
403 warn_message = (
404 "Date consistency check failed. The following dates are outside simulation range:\n"
405 + "\n".join(warn_strings)
406 )
407 self.add_to_error_list(warn_message)
408 # In C++, this would show a dialog for user override
409 # For Python, we'll just log the error and return False
411 return consistent
413 def simulate(self):
414 """
415 Main simulation loop. Coordinates colony, weather, immigration, treatments, and results.
416 Ported from C++ Simulate, preserving logic and comments.
417 """
418 if not self.ready_to_simulate():
419 self.add_to_error_list(
420 "Simulation not ready: colony or weather not initialized."
421 )
422 return
424 # Check date consistency before starting simulation (matches C++ behavior)
425 if not self.check_date_consistency(self.show_warnings):
426 return
428 # Set results frequency
429 res_freq = 7 if self.disp_weekly_data else 1
431 # Format string setup (simplified for Python)
432 delimiter = " "
433 if self.field_delimiter == 1:
434 delimiter = ","
435 elif self.field_delimiter == 2:
436 delimiter = "\t"
438 # Initialize results headers
439 self.set_default_headers()
440 self.results_text.clear()
441 self.results_file_text.clear()
443 # Add header rows (simplified)
444 self.results_text.append(" ".join(self.results_header))
446 # Generate Initial row showing exact initial conditions (like C++ version)
447 initial_row = self._generate_initial_conditions_row()
448 self.results_text.append(initial_row)
450 # Get simulation start and end
451 sim_start = self.get_sim_start()
452 sim_days = self.get_sim_days()
453 day_count = 1
454 tot_foraging_days = 0
456 # Get first event from weather
457 event = self.weather.get_day_event(sim_start)
459 # Main simulation loop
460 while event is not None and day_count <= sim_days:
461 # Requeening logic
462 self.colony.requeen_if_needed(
463 day_count,
464 event,
465 self.rq_egg_laying_delay,
466 self.rq_wkr_drn_ratio,
467 self.rq_enable_requeen,
468 self.rq_scheduled,
469 self.rq_queen_strength,
470 self.rq_once,
471 self.rq_requeen_date,
472 )
474 # Update bees
475 self.colony.update_bees(event, day_count)
477 # Immigration
478 if self.is_immigration_enabled() and self.is_immigration_window(event):
479 imm_mites_dict = self.get_immigration_mites(event)
480 # Convert dict to Mite object for colony.add_mites()
481 imm_mites = Mite(
482 imm_mites_dict["resistant"], imm_mites_dict["non_resistant"]
483 )
484 self.inc_immigrating_mites = imm_mites
485 self.colony.add_mites(imm_mites)
486 else:
487 self.inc_immigrating_mites = 0
489 # Update mites
490 self.colony.update_mites(event, day_count)
492 # Comb removal
493 if (
494 self.comb_remove_enable
495 and event.time.year == self.comb_remove_date.year
496 and event.time.month == self.comb_remove_date.month
497 and event.time.day == self.comb_remove_date.day
498 ):
499 self.colony.remove_drone_comb(self.comb_remove_pct)
501 # Pending events
502 self.colony.do_pending_events(event, day_count)
504 # Results output
505 if day_count % res_freq == 0:
506 # Collect results for the day using C++ compatible formatting
507 result_row = [
508 event.time.strftime("%m/%d/%Y"), # "%s"
509 "%6d" % self.colony.get_colony_size(), # "%6d"
510 "%8d" % self.colony.get_adult_drones(), # "%8d"
511 "%8d" % self.colony.get_adult_workers(), # "%8d"
512 "%8d" % self.colony.get_foragers(), # "%8d"
513 "%8d" % self.colony.get_active_foragers(), # "%8d"
514 "%7d" % self.colony.get_drone_brood(), # "%7d"
515 "%6d" % self.colony.get_worker_brood(), # "%6d"
516 "%6d" % self.colony.get_drone_larvae(), # "%6d"
517 "%6d" % self.colony.get_worker_larvae(), # "%6d"
518 "%6d" % self.colony.get_drone_eggs(), # "%6d"
519 "%6d" % self.colony.get_worker_eggs(), # "%6d"
520 "%6d" % self.colony.get_total_eggs_laid_today(), # "%6d"
521 "%7.2f" % self.colony.get_dd_today(), # "%7.2f"
522 "%6.2f" % self.colony.get_l_today(), # "%6.2f"
523 "%6.2f" % self.colony.get_n_today(), # "%6.2f"
524 "%8.2f" % self.colony.get_p_today(), # "%8.2f"
525 "%7.2f" % self.colony.get_dd_lower(), # "%7.2f"
526 "%6.2f" % self.colony.get_l_lower(), # "%6.2f"
527 "%8.2f" % self.colony.get_n_lower(), # "%8.2f"
528 "%6.2f" % self.colony.get_free_mites(), # "%6.2f"
529 "%6.2f" % self.colony.get_drone_brood_mites(), # "%6.2f"
530 "%6.2f" % self.colony.get_worker_brood_mites(), # "%6.2f"
531 "%6.2f" % self.colony.get_mites_per_drone_brood(), # "%6.2f"
532 "%6.2f" % self.colony.get_mites_per_worker_brood(), # "%6.2f"
533 "%6.0f" % self.colony.get_mites_dying_this_period(), # "%6.0f"
534 "%6.2f"
535 % self.colony.get_prop_mites_dying(), # "%6.2f" - changed from c++ code which uses 0 decimal places
536 "%8.1f" % self.colony.get_col_pollen(), # "%8.1f"
537 "%7.4f" % self.colony.get_pollen_pest_conc(), # "%7.4f"
538 "%8.1f" % self.colony.get_col_nectar(), # "%8.1f"
539 "%7.4f" % self.colony.get_nectar_pest_conc(), # "%7.4f"
540 "%6d"
541 % self.colony.get_dead_drone_larvae_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths
542 "%6d"
543 % self.colony.get_dead_worker_larvae_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths
544 "%6d"
545 % self.colony.get_dead_drone_adults_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths
546 "%6d"
547 % self.colony.get_dead_worker_adults_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths
548 "%6d"
549 % self.colony.get_dead_foragers_pesticide(), # "%6d" - FIXED: Use pesticide-specific deaths
550 "%8.3f" % self.colony.get_queen_strength(), # "%8.3f"
551 "%8.3f" % event.temp, # "%8.3f"
552 "%6.3f" % event.rainfall, # "%6.3f"
553 "%8.3f" % event.min_temp, # "%8.3f"
554 "%8.3f" % event.max_temp, # "%8.3f"
555 "%8.2f"
556 % event.daylight_hours, # "%8.2f" - KEY FORMATTING FOR DAYLIGHT HOURS
557 "%8.2f" % event.forage_inc, # "%8.2f"
558 "Yes" if event.is_forage_day() else "No", # "%s"
559 ]
560 self.results_text.append(delimiter.join(result_row))
562 if day_count % res_freq == 0:
563 self.colony.set_start_sample_period()
565 day_count += 1
566 if getattr(event, "is_forage_day", False):
567 tot_foraging_days += 1
568 event = self.weather.get_next_event()
570 self.results_ready = True
571 self.simulation_complete = True
572 self.colony.clear()
573 self.simulation_complete = False
575 # Immigration Operations
576 def set_immigration_type(self, im_type):
577 self.immigration_type = im_type
579 def get_immigration_type(self):
580 return self.immigration_type
582 def set_num_immigration_mites(self, mites):
583 # Store total mites (matching C++ logic where m_TotImmigratingMites.GetTotal() returns total)
584 self.tot_immigrating_mites = mites
586 def get_num_immigration_mites(self):
587 return self.tot_immigrating_mites
589 def set_immigration_start(self, start):
590 self.immigration_start_date = start
592 def get_immigration_start(self):
593 return self.immigration_start_date
595 def set_immigration_end(self, end):
596 self.immigration_end_date = end
598 def get_immigration_end(self):
599 return self.immigration_end_date
601 def set_immigration_enabled(self, enabled):
602 self.immigration_enabled = enabled
604 def is_immigration_enabled(self):
605 return self.immigration_enabled
607 def is_immigration_window(self, event):
608 today = event.time
609 # Match C++ logic: (today >= m_ImmigrationStartDate) && (today <= m_ImmigrationEndDate)
610 return (
611 today >= self.immigration_start_date and today <= self.immigration_end_date
612 )
614 def get_imm_pct_resistant(self):
615 return self.imm_mite_pct_resistant
617 def set_imm_pct_resistant(self, pctres):
618 self.imm_mite_pct_resistant = pctres
620 def get_immigration_mites(self, event):
621 """
622 Returns the number of immigration mites for a given event (date and colony count), supporting all immigration models.
623 Ported from C++ GetImmigrationMites.
625 This routine calculates the number of mites to immigrate on the
626 specified date. It also keeps track of the cumulative number of
627 mites that have migrated so far. First calculate the total quantity
628 of immigrating mites then return a CMite based on percent resistance to miticide
630 The equations of immigration were derived by identifying the desired function,
631 e.g. f(x) = A*Cos(x), then calculating the constants by setting the integral of
632 the function (over the range 0..1) to 1. This means that the area under the
633 curve is = 1. This ensures that 100% of m_TotImmigratingMites were added to the
634 colony. With the constants were established, a very simple numerical integration
635 is performed using sum(f(x)*DeltaX) for each day of immigration.
637 The immigration functions are:
639 Cosine -> f(x) = 1.188395*cos(x)
641 Sine -> f(x) = 1.57078*sin(PI*x)
643 Tangent -> f(x) = 2.648784*tan(1.5*x)
645 Exponential -> f(x) = (1.0/(e-2))*(exp(1 - (x)) - 1.0)
647 Logarithmic -> f(x) = -1.0*log(x) day #2 and on
649 Polynomial -> f(x) = 3.0*(x) - 1.5*(x*x)
651 In the case of Logarithmic, since there is an infinity at x=0, the
652 actual value of the integral over the range (0..DeltaX) is used on the first
653 day.
655 Mites only immigrate on foraging days.
657 Args:
658 event: An object with 'time' (datetime), 'num_colonies' (int), and 'is_forage_day' (bool) attributes.
659 Returns:
660 dict: {'resistant': float, 'non_resistant': float} number of immigration mites for the event's day.
661 """
663 # Immigration only occurs if enabled, on foraging days, and within the window
664 if not getattr(self, "immigration_enabled", False):
665 return {"resistant": 0, "non_resistant": 0}
666 the_date = event.time
667 # Check immigration window (matches C++ logic: today >= start AND today <= end)
668 if not (
669 the_date >= self.immigration_start_date
670 and the_date <= self.immigration_end_date
671 ):
672 return {"resistant": 0, "non_resistant": 0}
673 if not getattr(event, "is_forage_day", lambda: True)():
674 return {"resistant": 0, "non_resistant": 0}
676 sim_day_today = (the_date - self.sim_start_time).days + 1
677 sim_day_im_start = (self.immigration_start_date - self.sim_start_time).days + 1
678 sim_day_im_stop = (self.immigration_end_date - self.sim_start_time).days + 1
680 # Set cumulative immigration to 0 on first day
681 if sim_day_today == sim_day_im_start:
682 self.cum_immigrating_mites = 0
684 # Calculate proportion of days into immigration
685 im_prop = float(sim_day_today - sim_day_im_start) / float(
686 1 + sim_day_im_stop - sim_day_im_start
687 )
688 delta_x = 1.0 / (sim_day_im_stop - sim_day_im_start + 1)
689 x = im_prop + delta_x / 2
691 immigration_type = str(self.immigration_type).upper()
692 total_mites = 0.0
693 A = self.get_num_immigration_mites() # Total mites to distribute
695 if immigration_type == "NONE":
696 total_mites = 0.0
697 elif immigration_type == "COSINE":
698 total_mites = A * 1.188395 * math.cos(x) * delta_x
699 elif immigration_type == "EXPONENTIAL":
700 total_mites = (
701 A * (1.0 / (math.exp(1.0) - 2)) * (math.exp(1.0 - x) - 1.0) * delta_x
702 )
703 elif immigration_type == "LOGARITHMIC":
704 if im_prop == 0:
705 total_mites = A * (-1.0 * delta_x * math.log(delta_x) - delta_x)
706 else:
707 total_mites = A * (-1.0 * math.log(x) * delta_x)
708 elif immigration_type == "POLYNOMIAL":
709 total_mites = A * (3.0 * x - 1.5 * (x * x)) * delta_x
710 elif immigration_type == "SINE":
711 total_mites = A * 1.57078 * math.sin(math.pi * x) * delta_x
712 elif immigration_type == "TANGENT":
713 total_mites = A * 2.648784 * math.tan(1.5 * x) * delta_x
714 else:
715 total_mites = 0.0
717 if total_mites < 0.0:
718 total_mites = 0.0
720 resistant = total_mites * self.imm_mite_pct_resistant / 100.0
721 non_resistant = total_mites - resistant
722 self.cum_immigrating_mites += resistant + non_resistant
723 return {"resistant": resistant, "non_resistant": non_resistant}
725 # Implementation
726 def update_colony_parameters(self, param_name, param_value):
727 """
728 Updates colony parameters based on the provided name and value.
729 Ported from C++ UpdateColonyParameters, with pythonic improvements.
730 Args:
731 param_name (str): The name of the parameter to update.
732 param_value (str): The value to set for the parameter.
733 Returns:
734 bool: True if the parameter was updated, False otherwise.
735 """
737 name = param_name.strip().lower()
738 value = param_value.strip()
740 def parse_bool(val):
741 return str(val).lower() in ("1", "true", "yes")
743 def parse_date(val):
744 try:
745 return datetime.datetime.strptime(val, "%m/%d/%Y")
746 except Exception:
747 return None
749 # Session parameters
750 if name == "simstart":
751 dt = parse_date(value)
752 if dt:
753 self.set_sim_start(dt)
754 return True
755 self.add_to_error_list(f"Invalid simstart date: {value}")
756 return False
757 if name == "simend":
758 dt = parse_date(value)
759 if dt:
760 self.set_sim_end(dt)
761 return True
762 self.add_to_error_list(f"Invalid simend date: {value}")
763 return False
764 if name == "latitude":
765 try:
766 self.latitude = float(value)
767 if self.weather and hasattr(self.weather, "set_latitude"):
768 self.weather.set_latitude(self.latitude)
769 return True
770 except Exception:
771 self.add_to_error_list(f"Invalid latitude: {value}")
772 return False
774 # Initial Conditions parameters (IC) - Store directly in colony.m_init_cond
775 if name == "icdroneadults":
776 if self.colony and hasattr(self.colony, "m_init_cond"):
777 try:
778 self.colony.m_init_cond.m_droneAdultsField = int(value)
779 return True
780 except Exception:
781 self.add_to_error_list(f"Invalid icdroneadults: {value}")
782 return False
783 if name == "icworkeradults":
784 if self.colony and hasattr(self.colony, "m_init_cond"):
785 try:
786 self.colony.m_init_cond.m_workerAdultsField = int(value)
787 return True
788 except Exception:
789 self.add_to_error_list(f"Invalid icworkeradults: {value}")
790 return False
791 if name == "icdronebrood":
792 if self.colony and hasattr(self.colony, "m_init_cond"):
793 try:
794 self.colony.m_init_cond.m_droneBroodField = int(value)
795 return True
796 except Exception:
797 self.add_to_error_list(f"Invalid icdronebrood: {value}")
798 return False
799 if name == "icworkerbrood":
800 if self.colony and hasattr(self.colony, "m_init_cond"):
801 try:
802 self.colony.m_init_cond.m_workerBroodField = int(value)
803 return True
804 except Exception:
805 self.add_to_error_list(f"Invalid icworkerbrood: {value}")
806 return False
807 if name == "icdronelarvae":
808 if self.colony and hasattr(self.colony, "m_init_cond"):
809 try:
810 self.colony.m_init_cond.m_droneLarvaeField = int(value)
811 return True
812 except Exception:
813 self.add_to_error_list(f"Invalid icdronelarvae: {value}")
814 return False
815 if name == "icworkerlarvae":
816 if self.colony and hasattr(self.colony, "m_init_cond"):
817 try:
818 self.colony.m_init_cond.m_workerLarvaeField = int(value)
819 return True
820 except Exception:
821 self.add_to_error_list(f"Invalid icworkerlarvae: {value}")
822 return False
823 if name == "icdroneeggs":
824 if self.colony and hasattr(self.colony, "m_init_cond"):
825 try:
826 self.colony.m_init_cond.m_droneEggsField = int(value)
827 return True
828 except Exception:
829 self.add_to_error_list(f"Invalid icdroneeggs: {value}")
830 return False
831 if name == "icworkereggs":
832 if self.colony and hasattr(self.colony, "m_init_cond"):
833 try:
834 self.colony.m_init_cond.m_workerEggsField = int(value)
835 return True
836 except Exception:
837 self.add_to_error_list(f"Invalid icworkereggs: {value}")
838 return False
839 if name == "icqueenstrength":
840 if self.colony and hasattr(self.colony, "m_init_cond"):
841 try:
842 self.colony.m_init_cond.m_QueenStrength = float(value)
843 return True
844 except Exception:
845 self.add_to_error_list(f"Invalid icqueenstrength: {value}")
846 return False
847 if name == "icforagerlifespan":
848 if self.colony and hasattr(self.colony, "m_init_cond"):
849 try:
850 self.colony.m_init_cond.m_ForagerLifespan = int(value)
851 return True
852 except Exception:
853 self.add_to_error_list(f"Invalid icforagerlifespan: {value}")
854 return False
856 # Handle common parameter name variations (for user convenience)
857 if name == "queenstrength":
858 return self.update_colony_parameters("icqueenstrength", value)
859 if name == "foragerlifespan":
860 return self.update_colony_parameters("icforagerlifespan", value)
861 if name == "workeradults":
862 return self.update_colony_parameters("icworkeradults", value)
863 if name == "workerbrood":
864 return self.update_colony_parameters("icworkerbrood", value)
865 if name == "workereggs":
866 return self.update_colony_parameters("icworkereggs", value)
867 if name == "workerlarvae":
868 return self.update_colony_parameters("icworkerlarvae", value)
869 if name == "droneadults":
870 return self.update_colony_parameters("icdroneadults", value)
871 if name == "dronebrood":
872 return self.update_colony_parameters("icdronebrood", value)
873 if name == "droneeggs":
874 return self.update_colony_parameters("icdroneeggs", value)
875 if name == "dronelarvae":
876 return self.update_colony_parameters("icdronelarvae", value)
878 # Mite Parameters (ICDroneAdultInfest, etc.) - Following C++ session.cpp pattern
879 if name == "icdroneadultinfest":
880 if self.colony and hasattr(self.colony, "m_init_cond"):
881 try:
882 self.colony.m_init_cond.m_droneAdultInfestField = float(value)
883 return True
884 except Exception:
885 self.add_to_error_list(f"Invalid icdroneadultinfest: {value}")
886 return False
887 if name == "icdronebroodinfest":
888 if self.colony and hasattr(self.colony, "m_init_cond"):
889 try:
890 self.colony.m_init_cond.m_droneBroodInfestField = float(value)
891 return True
892 except Exception:
893 self.add_to_error_list(f"Invalid icdronebroodinfest: {value}")
894 return False
895 if name == "icdronemiteoffspring":
896 if self.colony and hasattr(self.colony, "m_init_cond"):
897 try:
898 self.colony.m_init_cond.m_droneMiteOffspringField = float(value)
899 return True
900 except Exception:
901 self.add_to_error_list(f"Invalid icdronemiteoffspring: {value}")
902 return False
903 if name == "icdronemitesurvivorship":
904 if self.colony and hasattr(self.colony, "m_init_cond"):
905 try:
906 self.colony.m_init_cond.m_droneMiteSurvivorshipField = float(value)
907 return True
908 except Exception:
909 self.add_to_error_list(f"Invalid icdronemitesurvivorship: {value}")
910 return False
911 if name == "icworkeradultinfest":
912 if self.colony and hasattr(self.colony, "m_init_cond"):
913 try:
914 self.colony.m_init_cond.m_workerAdultInfestField = float(value)
915 return True
916 except Exception:
917 self.add_to_error_list(f"Invalid icworkeradultinfest: {value}")
918 return False
919 if name == "icworkerbroodinfest":
920 if self.colony and hasattr(self.colony, "m_init_cond"):
921 try:
922 self.colony.m_init_cond.m_workerBroodInfestField = float(value)
923 return True
924 except Exception:
925 self.add_to_error_list(f"Invalid icworkerbroodinfest: {value}")
926 return False
927 if name == "icworkermiteoffspring":
928 if self.colony and hasattr(self.colony, "m_init_cond"):
929 try:
930 self.colony.m_init_cond.m_workerMiteOffspring = float(value)
931 return True
932 except Exception:
933 self.add_to_error_list(f"Invalid icworkermiteoffspring: {value}")
934 return False
935 if name == "icworkermitesurvivorship":
936 if self.colony and hasattr(self.colony, "m_init_cond"):
937 try:
938 self.colony.m_init_cond.m_workerMiteSurvivorship = float(value)
939 return True
940 except Exception:
941 self.add_to_error_list(f"Invalid icworkermitesurvivorship: {value}")
942 return False
943 if name == "initmitepctresistant":
944 try:
945 self.init_mite_pct_resistant = float(value)
946 return True
947 except Exception:
948 self.add_to_error_list(f"Invalid initmitepctresistant: {value}")
949 return False
951 # AI/Pesticide Parameters (following C++ session.cpp pattern)
952 if name == "ainame":
953 if self.colony and hasattr(self.colony, "m_epadata"):
954 self.colony.m_epadata.m_AI_Name = value
955 return True
956 if name == "aiadultslope":
957 if self.colony and hasattr(self.colony, "m_epadata"):
958 try:
959 self.colony.m_epadata.m_AI_AdultSlope = float(value)
960 return True
961 except Exception:
962 self.add_to_error_list(f"Invalid aiadultslope: {value}")
963 return False
964 if name == "aiadultld50":
965 if self.colony and hasattr(self.colony, "m_epadata"):
966 try:
967 self.colony.m_epadata.m_AI_AdultLD50 = float(value)
968 return True
969 except Exception:
970 self.add_to_error_list(f"Invalid aiadultld50: {value}")
971 return False
972 if name == "aiadultslopecontact":
973 if self.colony and hasattr(self.colony, "m_epadata"):
974 try:
975 self.colony.m_epadata.m_AI_AdultSlope_Contact = float(value)
976 return True
977 except Exception:
978 self.add_to_error_list(f"Invalid aiadultslopecontact: {value}")
979 return False
980 if name == "aiadultld50contact":
981 if self.colony and hasattr(self.colony, "m_epadata"):
982 try:
983 self.colony.m_epadata.m_AI_AdultLD50_Contact = float(value)
984 return True
985 except Exception:
986 self.add_to_error_list(f"Invalid aiadultld50contact: {value}")
987 return False
988 if name == "ailarvaslope":
989 if self.colony and hasattr(self.colony, "m_epadata"):
990 try:
991 self.colony.m_epadata.m_AI_LarvaSlope = float(value)
992 return True
993 except Exception:
994 self.add_to_error_list(f"Invalid ailarvaslope: {value}")
995 return False
996 if name == "ailarvald50":
997 if self.colony and hasattr(self.colony, "m_epadata"):
998 try:
999 self.colony.m_epadata.m_AI_LarvaLD50 = float(value)
1000 return True
1001 except Exception:
1002 self.add_to_error_list(f"Invalid ailarvald50: {value}")
1003 return False
1004 if name == "aikow":
1005 if self.colony and hasattr(self.colony, "m_epadata"):
1006 try:
1007 self.colony.m_epadata.m_AI_KOW = float(value)
1008 return True
1009 except Exception:
1010 self.add_to_error_list(f"Invalid aikow: {value}")
1011 return False
1012 if name == "aikoc":
1013 if self.colony and hasattr(self.colony, "m_epadata"):
1014 try:
1015 self.colony.m_epadata.m_AI_KOC = float(value)
1016 return True
1017 except Exception:
1018 self.add_to_error_list(f"Invalid aikoc: {value}")
1019 return False
1020 if name == "aihalflife":
1021 if self.colony and hasattr(self.colony, "m_epadata"):
1022 try:
1023 self.colony.m_epadata.m_AI_HalfLife = float(value)
1024 return True
1025 except Exception:
1026 self.add_to_error_list(f"Invalid aihalflife: {value}")
1027 return False
1028 if name == "aicontactfactor":
1029 if self.colony and hasattr(self.colony, "m_epadata"):
1030 try:
1031 self.colony.m_epadata.m_AI_ContactFactor = float(value)
1032 return True
1033 except Exception:
1034 self.add_to_error_list(f"Invalid aicontactfactor: {value}")
1035 return False
1037 # Consumption Parameters (CL4Pollen, etc.) - following C++ session.cpp pattern
1038 if name == "cl4pollen":
1039 if self.colony and hasattr(self.colony, "m_epadata"):
1040 try:
1041 self.colony.m_epadata.m_C_L4_Pollen = float(value)
1042 return True
1043 except Exception:
1044 self.add_to_error_list(f"Invalid cl4pollen: {value}")
1045 return False
1046 if name == "cl4nectar":
1047 if self.colony and hasattr(self.colony, "m_epadata"):
1048 try:
1049 self.colony.m_epadata.m_C_L4_Nectar = float(value)
1050 return True
1051 except Exception:
1052 self.add_to_error_list(f"Invalid cl4nectar: {value}")
1053 return False
1054 if name == "cl5pollen":
1055 if self.colony and hasattr(self.colony, "m_epadata"):
1056 try:
1057 self.colony.m_epadata.m_C_L5_Pollen = float(value)
1058 return True
1059 except Exception:
1060 self.add_to_error_list(f"Invalid cl5pollen: {value}")
1061 return False
1062 if name == "cl5nectar":
1063 if self.colony and hasattr(self.colony, "m_epadata"):
1064 try:
1065 self.colony.m_epadata.m_C_L5_Nectar = float(value)
1066 return True
1067 except Exception:
1068 self.add_to_error_list(f"Invalid cl5nectar: {value}")
1069 return False
1070 if name == "cldpollen":
1071 if self.colony and hasattr(self.colony, "m_epadata"):
1072 try:
1073 self.colony.m_epadata.m_C_LD_Pollen = float(value)
1074 return True
1075 except Exception:
1076 self.add_to_error_list(f"Invalid cldpollen: {value}")
1077 return False
1078 if name == "cldnectar":
1079 if self.colony and hasattr(self.colony, "m_epadata"):
1080 try:
1081 self.colony.m_epadata.m_C_LD_Nectar = float(value)
1082 return True
1083 except Exception:
1084 self.add_to_error_list(f"Invalid cldnectar: {value}")
1085 return False
1086 if name == "ca13pollen":
1087 if self.colony and hasattr(self.colony, "m_epadata"):
1088 try:
1089 self.colony.m_epadata.m_C_A13_Pollen = float(value)
1090 return True
1091 except Exception:
1092 self.add_to_error_list(f"Invalid ca13pollen: {value}")
1093 return False
1094 if name == "ca13nectar":
1095 if self.colony and hasattr(self.colony, "m_epadata"):
1096 try:
1097 self.colony.m_epadata.m_C_A13_Nectar = float(value)
1098 return True
1099 except Exception:
1100 self.add_to_error_list(f"Invalid ca13nectar: {value}")
1101 return False
1102 if name == "ca410pollen":
1103 if self.colony and hasattr(self.colony, "m_epadata"):
1104 try:
1105 self.colony.m_epadata.m_C_A410_Pollen = float(value)
1106 return True
1107 except Exception:
1108 self.add_to_error_list(f"Invalid ca410pollen: {value}")
1109 return False
1110 if name == "ca410nectar":
1111 if self.colony and hasattr(self.colony, "m_epadata"):
1112 try:
1113 self.colony.m_epadata.m_C_A410_Nectar = float(value)
1114 return True
1115 except Exception:
1116 self.add_to_error_list(f"Invalid ca410nectar: {value}")
1117 return False
1118 if name == "ca1120pollen":
1119 if self.colony and hasattr(self.colony, "m_epadata"):
1120 try:
1121 self.colony.m_epadata.m_C_A1120_Pollen = float(value)
1122 return True
1123 except Exception:
1124 self.add_to_error_list(f"Invalid ca1120pollen: {value}")
1125 return False
1126 if name == "ca1120nectar":
1127 if self.colony and hasattr(self.colony, "m_epadata"):
1128 try:
1129 self.colony.m_epadata.m_C_A1120_Nectar = float(value)
1130 return True
1131 except Exception:
1132 self.add_to_error_list(f"Invalid ca1120nectar: {value}")
1133 return False
1134 if name == "cadpollen":
1135 if self.colony and hasattr(self.colony, "m_epadata"):
1136 try:
1137 self.colony.m_epadata.m_C_AD_Pollen = float(value)
1138 return True
1139 except Exception:
1140 self.add_to_error_list(f"Invalid cadpollen: {value}")
1141 return False
1142 if name == "cadnectar":
1143 if self.colony and hasattr(self.colony, "m_epadata"):
1144 try:
1145 self.colony.m_epadata.m_C_AD_Nectar = float(value)
1146 return True
1147 except Exception:
1148 self.add_to_error_list(f"Invalid cadnectar: {value}")
1149 return False
1150 if name == "cforagerpollen":
1151 if self.colony and hasattr(self.colony, "m_epadata"):
1152 try:
1153 self.colony.m_epadata.m_C_Forager_Pollen = float(value)
1154 return True
1155 except Exception:
1156 self.add_to_error_list(f"Invalid cforagerpollen: {value}")
1157 return False
1158 if name == "cforagernectar":
1159 if self.colony and hasattr(self.colony, "m_epadata"):
1160 try:
1161 self.colony.m_epadata.m_C_Forager_Nectar = float(value)
1162 return True
1163 except Exception:
1164 self.add_to_error_list(f"Invalid cforagernectar: {value}")
1165 return False
1167 # Foraging Parameters (IPollenTrips, etc.) - following C++ session.cpp pattern
1168 if name == "ipollentrips":
1169 if self.colony and hasattr(self.colony, "m_epadata"):
1170 try:
1171 self.colony.m_epadata.m_I_PollenTrips = int(
1172 float(value)
1173 ) # Convert float to int like C++
1174 return True
1175 except Exception:
1176 self.add_to_error_list(f"Invalid ipollentrips: {value}")
1177 return False
1178 if name == "inectartrips":
1179 if self.colony and hasattr(self.colony, "m_epadata"):
1180 try:
1181 self.colony.m_epadata.m_I_NectarTrips = int(
1182 float(value)
1183 ) # Convert float to int like C++
1184 return True
1185 except Exception:
1186 self.add_to_error_list(f"Invalid inectartrips: {value}")
1187 return False
1188 if name == "ipercentnectarforagers":
1189 if self.colony and hasattr(self.colony, "m_epadata"):
1190 try:
1191 self.colony.m_epadata.m_I_PercentNectarForagers = float(value)
1192 return True
1193 except Exception:
1194 self.add_to_error_list(f"Invalid ipercentnectarforagers: {value}")
1195 return False
1196 if name == "ipollenload":
1197 if self.colony and hasattr(self.colony, "m_epadata"):
1198 try:
1199 self.colony.m_epadata.m_I_PollenLoad = float(value)
1200 return True
1201 except Exception:
1202 self.add_to_error_list(f"Invalid ipollenload: {value}")
1203 return False
1204 if name == "inectarload":
1205 if self.colony and hasattr(self.colony, "m_epadata"):
1206 try:
1207 self.colony.m_epadata.m_I_NectarLoad = float(value)
1208 return True
1209 except Exception:
1210 self.add_to_error_list(f"Invalid inectarload: {value}")
1211 return False
1213 # Feature Flags (FoliarEnabled, etc.) - following C++ session.cpp pattern
1214 if name == "foliarenabled":
1215 if self.colony and hasattr(self.colony, "m_epadata"):
1216 self.colony.m_epadata.m_FoliarEnabled = parse_bool(value)
1217 return True
1218 if name == "soilenabled":
1219 if self.colony and hasattr(self.colony, "m_epadata"):
1220 self.colony.m_epadata.m_SoilEnabled = parse_bool(value)
1221 return True
1222 if name == "seedenabled":
1223 if self.colony and hasattr(self.colony, "m_epadata"):
1224 self.colony.m_epadata.m_SeedEnabled = parse_bool(value)
1225 return True
1226 if name == "necpolfileenable":
1227 if self.colony and hasattr(self.colony, "m_epadata"):
1228 self.colony.m_epadata.m_NecPolFileEnabled = parse_bool(value)
1229 return True
1231 # Exposure Parameters (EAppRate, etc.) - following C++ session.cpp pattern
1232 if name == "eapprate":
1233 if self.colony and hasattr(self.colony, "m_epadata"):
1234 try:
1235 self.colony.m_epadata.m_E_AppRate = float(value)
1236 return True
1237 except Exception:
1238 self.add_to_error_list(f"Invalid eapprate: {value}")
1239 return False
1240 if name == "esoiltheta":
1241 if self.colony and hasattr(self.colony, "m_epadata"):
1242 try:
1243 self.colony.m_epadata.m_E_SoilTheta = float(value)
1244 return True
1245 except Exception:
1246 self.add_to_error_list(f"Invalid esoiltheta: {value}")
1247 return False
1248 if name == "esoilp":
1249 if self.colony and hasattr(self.colony, "m_epadata"):
1250 try:
1251 self.colony.m_epadata.m_E_SoilP = float(value)
1252 return True
1253 except Exception:
1254 self.add_to_error_list(f"Invalid esoilp: {value}")
1255 return False
1256 if name == "esoilfoc":
1257 if self.colony and hasattr(self.colony, "m_epadata"):
1258 try:
1259 self.colony.m_epadata.m_E_SoilFoc = float(value)
1260 return True
1261 except Exception:
1262 self.add_to_error_list(f"Invalid esoilfoc: {value}")
1263 return False
1264 if name == "esoilconcentration":
1265 if self.colony and hasattr(self.colony, "m_epadata"):
1266 try:
1267 self.colony.m_epadata.m_E_SoilConcentration = float(value)
1268 return True
1269 except Exception:
1270 self.add_to_error_list(f"Invalid esoilconcentration: {value}")
1271 return False
1272 if name == "eseedconcentration":
1273 if self.colony and hasattr(self.colony, "m_epadata"):
1274 try:
1275 self.colony.m_epadata.m_E_SeedConcentration = float(value)
1276 return True
1277 except Exception:
1278 self.add_to_error_list(f"Invalid eseedconcentration: {value}")
1279 return False
1281 # Foliar Date Parameters - following C++ session.cpp pattern
1282 if name == "foliarappdate":
1283 if self.colony and hasattr(self.colony, "m_epadata"):
1284 dt = parse_date(value)
1285 if dt:
1286 self.colony.m_epadata.m_FoliarAppDate = dt
1287 return True
1288 self.add_to_error_list(
1289 f"Invalid foliarappdate format (expected MM/DD/YYYY): {value}"
1290 )
1291 return False
1292 if name == "foliarforagebegin":
1293 if self.colony and hasattr(self.colony, "m_epadata"):
1294 dt = parse_date(value)
1295 if dt:
1296 self.colony.m_epadata.m_FoliarForageBegin = dt
1297 return True
1298 self.add_to_error_list(
1299 f"Invalid foliarforagebegin format (expected MM/DD/YYYY): {value}"
1300 )
1301 return False
1302 if name == "foliarforageend":
1303 if self.colony and hasattr(self.colony, "m_epadata"):
1304 dt = parse_date(value)
1305 if dt:
1306 self.colony.m_epadata.m_FoliarForageEnd = dt
1307 return True
1308 self.add_to_error_list(
1309 f"Invalid foliarforageend format (expected MM/DD/YYYY): {value}"
1310 )
1311 return False
1312 if name == "soilforagebegin":
1313 if self.colony and hasattr(self.colony, "m_epadata"):
1314 dt = parse_date(value)
1315 if dt:
1316 self.colony.m_epadata.m_SoilForageBegin = dt
1317 return True
1318 self.add_to_error_list(
1319 f"Invalid soilforagebegin format (expected MM/DD/YYYY): {value}"
1320 )
1321 return False
1322 if name == "soilforageend":
1323 if self.colony and hasattr(self.colony, "m_epadata"):
1324 dt = parse_date(value)
1325 if dt:
1326 self.colony.m_epadata.m_SoilForageEnd = dt
1327 return True
1328 self.add_to_error_list(
1329 f"Invalid soilforageend format (expected MM/DD/YYYY): {value}"
1330 )
1331 return False
1332 if name == "seedforagebegin":
1333 if self.colony and hasattr(self.colony, "m_epadata"):
1334 dt = parse_date(value)
1335 if dt:
1336 self.colony.m_epadata.m_SeedForageBegin = dt
1337 return True
1338 self.add_to_error_list(
1339 f"Invalid seedforagebegin format (expected MM/DD/YYYY): {value}"
1340 )
1341 return False
1342 if name == "seedforageend":
1343 if self.colony and hasattr(self.colony, "m_epadata"):
1344 dt = parse_date(value)
1345 if dt:
1346 self.colony.m_epadata.m_SeedForageEnd = dt
1347 return True
1348 self.add_to_error_list(
1349 f"Invalid seedforageend format (expected MM/DD/YYYY): {value}"
1350 )
1351 return False
1353 # Resource Management Parameters (following C++ session.cpp pattern)
1354 if name == "initcolnectar":
1355 if self.colony:
1356 try:
1357 self.colony.m_ColonyNecInitAmount = float(value)
1358 return True
1359 except Exception:
1360 self.add_to_error_list(f"Invalid initcolnectar: {value}")
1361 return False
1362 if name == "initcolpollen":
1363 if self.colony:
1364 try:
1365 self.colony.m_ColonyPolInitAmount = float(value)
1366 return True
1367 except Exception:
1368 self.add_to_error_list(f"Invalid initcolpollen: {value}")
1369 return False
1370 if name == "maxcolnectar":
1371 if self.colony:
1372 try:
1373 self.colony.m_ColonyNecMaxAmount = float(value)
1374 return True
1375 except Exception:
1376 self.add_to_error_list(f"Invalid maxcolnectar: {value}")
1377 return False
1378 if name == "maxcolpollen":
1379 if self.colony:
1380 try:
1381 self.colony.m_ColonyPolMaxAmount = float(value)
1382 return True
1383 except Exception:
1384 self.add_to_error_list(f"Invalid maxcolpollen: {value}")
1385 return False
1386 if name == "suppollenenable":
1387 if self.colony:
1388 self.colony.m_SuppPollenEnabled = parse_bool(value)
1389 return True
1390 if name == "supnectarenable":
1391 if self.colony:
1392 self.colony.m_SuppNectarEnabled = parse_bool(value)
1393 return True
1394 if name == "suppollenamount":
1395 if self.colony and hasattr(self.colony, "m_SuppPollen"):
1396 try:
1397 self.colony.m_SuppPollen.m_StartingAmount = float(value)
1398 return True
1399 except Exception:
1400 self.add_to_error_list(f"Invalid suppollenamount: {value}")
1401 return False
1402 if name == "suppollenbegin":
1403 if self.colony and hasattr(self.colony, "m_SuppPollen"):
1404 try:
1405 # Parse date in format MM/DD/YYYY (like C++)
1406 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y")
1407 self.colony.m_SuppPollen.m_BeginDate = date_obj
1408 return True
1409 except Exception:
1410 self.add_to_error_list(
1411 f"Invalid suppollenbegin date format (expected MM/DD/YYYY): {value}"
1412 )
1413 return False
1414 if name == "suppollenend":
1415 if self.colony and hasattr(self.colony, "m_SuppPollen"):
1416 try:
1417 # Parse date in format MM/DD/YYYY (like C++)
1418 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y")
1419 self.colony.m_SuppPollen.m_EndDate = date_obj
1420 return True
1421 except Exception:
1422 self.add_to_error_list(
1423 f"Invalid suppollenend date format (expected MM/DD/YYYY): {value}"
1424 )
1425 return False
1426 if name == "supnectaramount":
1427 if self.colony and hasattr(self.colony, "m_SuppNectar"):
1428 try:
1429 self.colony.m_SuppNectar.m_StartingAmount = float(value)
1430 return True
1431 except Exception:
1432 self.add_to_error_list(f"Invalid supnectaramount: {value}")
1433 return False
1434 if name == "supnectarbegin":
1435 if self.colony and hasattr(self.colony, "m_SuppNectar"):
1436 try:
1437 # Parse date in format MM/DD/YYYY (like C++)
1438 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y")
1439 self.colony.m_SuppNectar.m_BeginDate = date_obj
1440 return True
1441 except Exception:
1442 self.add_to_error_list(
1443 f"Invalid supnectarbegin date format (expected MM/DD/YYYY): {value}"
1444 )
1445 return False
1446 if name == "supnectarend":
1447 if self.colony and hasattr(self.colony, "m_SuppNectar"):
1448 try:
1449 # Parse date in format MM/DD/YYYY (like C++)
1450 date_obj = datetime.datetime.strptime(value, "%m/%d/%Y")
1451 self.colony.m_SuppNectar.m_EndDate = date_obj
1452 return True
1453 except Exception:
1454 self.add_to_error_list(
1455 f"Invalid supnectarend date format (expected MM/DD/YYYY): {value}"
1456 )
1457 return False
1458 if name == "foragermaxprop":
1459 if self.colony and hasattr(self.colony, "foragers"):
1460 try:
1461 self.colony.foragers.set_prop_actual_foragers(float(value))
1462 return True
1463 except Exception:
1464 self.add_to_error_list(f"Invalid foragermaxprop: {value}")
1465 return False
1466 if name == "needresourcestolive":
1467 if self.colony:
1468 self.colony.m_NoResourceKillsColony = parse_bool(value)
1469 return True
1471 # Life Stage Transition Parameters (following C++ session.cpp pattern)
1472 if name == "etolxitionen":
1473 # EtoL Transition Enable - theColony.m_InitCond.m_EggTransitionDRV.SetEnabled(Value == "true")
1474 if self.colony and hasattr(self.colony, "m_init_cond"):
1475 self.colony.m_init_cond.m_EggTransitionDRV.set_enabled(
1476 parse_bool(value)
1477 )
1478 return True
1479 return False
1480 if name == "ltobxitionen":
1481 # LtoB Transition Enable - theColony.m_InitCond.m_LarvaeTransitionDRV.SetEnabled(Value == "true")
1482 if self.colony and hasattr(self.colony, "m_init_cond"):
1483 self.colony.m_init_cond.m_LarvaeTransitionDRV.set_enabled(
1484 parse_bool(value)
1485 )
1486 return True
1487 return False
1488 if name == "btoaxitionen":
1489 # BtoA Transition Enable - theColony.m_InitCond.m_BroodTransitionDRV.SetEnabled(Value == "true")
1490 if self.colony and hasattr(self.colony, "m_init_cond"):
1491 self.colony.m_init_cond.m_BroodTransitionDRV.set_enabled(
1492 parse_bool(value)
1493 )
1494 return True
1495 return False
1496 if name == "atofxitionen":
1497 # AtoF Transition Enable - theColony.m_InitCond.m_AdultTransitionDRV.SetEnabled(Value == "true")
1498 if self.colony and hasattr(self.colony, "m_init_cond"):
1499 self.colony.m_init_cond.m_AdultTransitionDRV.set_enabled(
1500 parse_bool(value)
1501 )
1502 return True
1503 return False
1505 # Lifespan Control Parameters (following C++ session.cpp pattern)
1506 if name == "alifespanen":
1507 # Adult Lifespan Enable - theColony.m_InitCond.m_AdultLifespanDRV.SetEnabled(Value == "true")
1508 if self.colony and hasattr(self.colony, "m_init_cond"):
1509 self.colony.m_init_cond.m_AdultLifespanDRV.set_enabled(
1510 parse_bool(value)
1511 )
1512 return True
1513 return False
1514 if name == "flifespanen":
1515 # Forager Lifespan Enable - theColony.m_InitCond.m_ForagerLifespanDRV.SetEnabled(Value == "true")
1516 if self.colony and hasattr(self.colony, "m_init_cond"):
1517 self.colony.m_init_cond.m_ForagerLifespanDRV.set_enabled(
1518 parse_bool(value)
1519 )
1520 return True
1521 return False
1523 # Adult Aging Delay Parameters (following C++ session.cpp pattern)
1524 if name == "adultagedelay":
1525 # Adult Age Delay - theColony.SetAdultAgingDelay(atoi(Value))
1526 if self.colony:
1527 try:
1528 self.colony.set_adult_aging_delay(int(value))
1529 return True
1530 except Exception:
1531 self.add_to_error_list(f"Invalid adultagedelay: {value}")
1532 return False
1533 return False
1534 if name == "adultagedelayeggthreshold":
1535 # Adult Age Delay Egg Threshold - theColony.SetAdultAgingDelayEggThreshold(atoi(Value))
1536 if self.colony:
1537 try:
1538 self.colony.set_adult_aging_delay_egg_threshold(int(value))
1539 return True
1540 except Exception:
1541 self.add_to_error_list(
1542 f"Invalid adultagedelayeggthreshold: {value}"
1543 )
1544 return False
1545 return False
1547 # Life Stage Transition Percentage Parameters (following C++ session.cpp pattern)
1548 if name == "etolxition":
1549 # Egg to Larva Transition - theColony.m_InitCond.m_EggTransitionDRV.AddItem() or ClearAll()
1550 if self.colony and hasattr(self.colony, "m_init_cond"):
1551 if value.lower() == "clear":
1552 self.colony.m_init_cond.m_EggTransitionDRV.clear_all()
1553 return True
1554 else:
1555 try:
1556 # Parse comma-separated format: StartDate,EndDate,Percentage
1557 parts = [part.strip() for part in value.split(",")]
1558 if len(parts) >= 3:
1559 start_date_str, end_date_str, percentage_str = (
1560 parts[0],
1561 parts[1],
1562 parts[2],
1563 )
1564 start_date = parse_date(start_date_str)
1565 end_date = parse_date(end_date_str)
1566 percentage = float(percentage_str)
1567 if start_date and end_date:
1568 self.colony.m_init_cond.m_EggTransitionDRV.add_item(
1569 start_date, end_date, percentage
1570 )
1571 return True
1572 except Exception:
1573 self.add_to_error_list(f"Invalid etolxition format: {value}")
1574 return False
1575 return False
1576 if name == "ltobxition":
1577 # Larva to Brood Transition - theColony.m_InitCond.m_LarvaeTransitionDRV.AddItem() or ClearAll()
1578 if self.colony and hasattr(self.colony, "m_init_cond"):
1579 if value.lower() == "clear":
1580 self.colony.m_init_cond.m_LarvaeTransitionDRV.clear_all()
1581 return True
1582 else:
1583 try:
1584 # Parse comma-separated format: StartDate,EndDate,Percentage
1585 parts = [part.strip() for part in value.split(",")]
1586 if len(parts) >= 3:
1587 start_date_str, end_date_str, percentage_str = (
1588 parts[0],
1589 parts[1],
1590 parts[2],
1591 )
1592 start_date = parse_date(start_date_str)
1593 end_date = parse_date(end_date_str)
1594 percentage = float(percentage_str)
1595 if start_date and end_date:
1596 self.colony.m_init_cond.m_LarvaeTransitionDRV.add_item(
1597 start_date, end_date, percentage
1598 )
1599 return True
1600 except Exception:
1601 self.add_to_error_list(f"Invalid ltobxition format: {value}")
1602 return False
1603 return False
1604 if name == "btoaxition":
1605 # Brood to Adult Transition - theColony.m_InitCond.m_BroodTransitionDRV.AddItem() or ClearAll()
1606 if self.colony and hasattr(self.colony, "m_init_cond"):
1607 if value.lower() == "clear":
1608 self.colony.m_init_cond.m_BroodTransitionDRV.clear_all()
1609 return True
1610 else:
1611 try:
1612 # Parse comma-separated format: StartDate,EndDate,Percentage
1613 parts = [part.strip() for part in value.split(",")]
1614 if len(parts) >= 3:
1615 start_date_str, end_date_str, percentage_str = (
1616 parts[0],
1617 parts[1],
1618 parts[2],
1619 )
1620 start_date = parse_date(start_date_str)
1621 end_date = parse_date(end_date_str)
1622 percentage = float(percentage_str)
1623 if start_date and end_date:
1624 self.colony.m_init_cond.m_BroodTransitionDRV.add_item(
1625 start_date, end_date, percentage
1626 )
1627 return True
1628 except Exception:
1629 self.add_to_error_list(f"Invalid btoaxition format: {value}")
1630 return False
1631 return False
1632 if name == "atofxition":
1633 # Adult to Forager Transition - theColony.m_InitCond.m_AdultTransitionDRV.AddItem() or ClearAll()
1634 if self.colony and hasattr(self.colony, "m_init_cond"):
1635 if value.lower() == "clear":
1636 self.colony.m_init_cond.m_AdultTransitionDRV.clear_all()
1637 return True
1638 else:
1639 try:
1640 # Parse comma-separated format: StartDate,EndDate,Percentage
1641 parts = [part.strip() for part in value.split(",")]
1642 if len(parts) >= 3:
1643 start_date_str, end_date_str, percentage_str = (
1644 parts[0],
1645 parts[1],
1646 parts[2],
1647 )
1648 start_date = parse_date(start_date_str)
1649 end_date = parse_date(end_date_str)
1650 percentage = float(percentage_str)
1651 if start_date and end_date:
1652 self.colony.m_init_cond.m_AdultTransitionDRV.add_item(
1653 start_date, end_date, percentage
1654 )
1655 return True
1656 except Exception:
1657 self.add_to_error_list(f"Invalid atofxition format: {value}")
1658 return False
1659 return False
1661 # Lifespan Data Parameters (following C++ session.cpp pattern)
1662 if name == "alifespan":
1663 # Adult Lifespan - theColony.m_InitCond.m_AdultLifespanDRV.AddItem() or ClearAll()
1664 if self.colony and hasattr(self.colony, "m_init_cond"):
1665 if value.lower() == "clear":
1666 self.colony.m_init_cond.m_AdultLifespanDRV.clear_all()
1667 return True
1668 else:
1669 try:
1670 # Parse comma-separated format: StartDate,EndDate,LifespanDays
1671 parts = [part.strip() for part in value.split(",")]
1672 if len(parts) >= 3:
1673 start_date_str, end_date_str, lifespan_str = (
1674 parts[0],
1675 parts[1],
1676 parts[2],
1677 )
1678 start_date = parse_date(start_date_str)
1679 end_date = parse_date(end_date_str)
1680 lifespan_days = float(lifespan_str)
1681 # Apply C++ constraint: Adult bee age constraint (7-21 days)
1682 if start_date and end_date and 7 <= lifespan_days <= 21:
1683 self.colony.m_init_cond.m_AdultLifespanDRV.add_item(
1684 start_date, end_date, lifespan_days
1685 )
1686 return True
1687 elif not (7 <= lifespan_days <= 21):
1688 self.add_to_error_list(
1689 f"Adult lifespan must be 7-21 days, got: {lifespan_days}"
1690 )
1691 return False
1692 except Exception:
1693 self.add_to_error_list(f"Invalid alifespan format: {value}")
1694 return False
1695 return False
1696 if name == "flifespan":
1697 # Forager Lifespan - theColony.m_InitCond.m_ForagerLifespanDRV.AddItem() or ClearAll()
1698 if self.colony and hasattr(self.colony, "m_init_cond"):
1699 if value.lower() == "clear":
1700 self.colony.m_init_cond.m_ForagerLifespanDRV.clear_all()
1701 return True
1702 else:
1703 try:
1704 # Parse comma-separated format: StartDate,EndDate,LifespanDays
1705 parts = [part.strip() for part in value.split(",")]
1706 if len(parts) >= 3:
1707 start_date_str, end_date_str, lifespan_str = (
1708 parts[0],
1709 parts[1],
1710 parts[2],
1711 )
1712 start_date = parse_date(start_date_str)
1713 end_date = parse_date(end_date_str)
1714 lifespan_days = float(lifespan_str)
1715 # Apply C++ constraint: Forager lifespan constraint (0-20 days)
1716 if start_date and end_date and 0 <= lifespan_days <= 20:
1717 self.colony.m_init_cond.m_ForagerLifespanDRV.add_item(
1718 start_date, end_date, lifespan_days
1719 )
1720 return True
1721 elif not (0 <= lifespan_days <= 20):
1722 self.add_to_error_list(
1723 f"Forager lifespan must be 0-20 days, got: {lifespan_days}"
1724 )
1725 return False
1726 except Exception:
1727 self.add_to_error_list(f"Invalid flifespan format: {value}")
1728 return False
1729 return False
1731 # Varroa Treatment Parameters (following C++ session.cpp pattern)
1732 if name == "vtenable":
1733 self.vt_enable = parse_bool(value)
1734 return True
1736 # Immigration parameters
1737 if name == "immenabled":
1738 self.immigration_enabled = parse_bool(value)
1739 return True
1740 if name == "immtype":
1741 self.immigration_type = value
1742 return True
1743 if name == "totalimmmites":
1744 try:
1745 self.tot_immigrating_mites = int(value)
1746 return True
1747 except Exception:
1748 self.add_to_error_list(f"Invalid totalimmmites: {value}")
1749 return False
1750 if name == "pctimmmitesresistant":
1751 try:
1752 self.imm_mite_pct_resistant = float(value)
1753 return True
1754 except Exception:
1755 self.add_to_error_list(f"Invalid pctimmmitesresistant: {value}")
1756 return False
1757 if name == "immstart":
1758 dt = parse_date(value)
1759 if dt:
1760 self.immigration_start_date = dt
1761 return True
1762 self.add_to_error_list(f"Invalid immstart date: {value}")
1763 return False
1764 if name == "immend":
1765 dt = parse_date(value)
1766 if dt:
1767 self.immigration_end_date = dt
1768 return True
1769 self.add_to_error_list(f"Invalid immend date: {value}")
1770 return False
1771 if name == "immenabled":
1772 self.immigration_enabled = parse_bool(value)
1773 return True
1775 # Requeening parameters
1776 if name == "rqegglaydelay":
1777 try:
1778 self.rq_egg_laying_delay = int(value)
1779 return True
1780 except Exception:
1781 self.add_to_error_list(f"Invalid rqegglaydelay: {value}")
1782 return False
1783 if name == "rqwkrdrnratio":
1784 try:
1785 self.rq_wkr_drn_ratio = float(value)
1786 return True
1787 except Exception:
1788 self.add_to_error_list(f"Invalid rqwkrdrnratio: {value}")
1789 return False
1790 if name == "rqrequeendate":
1791 dt = parse_date(value)
1792 if dt:
1793 self.rq_requeen_date = dt
1794 return True
1795 self.add_to_error_list(f"Invalid rqrequeendate: {value}")
1796 return False
1797 if name == "rqenablerequeen":
1798 self.rq_enable_requeen = parse_bool(value)
1799 return True
1800 if name == "rqscheduled":
1801 self.rq_scheduled = 0 if parse_bool(value) else 1
1802 return True
1803 if name == "rqqueenstrength":
1804 try:
1805 self.rq_queen_strength = float(value)
1806 if self.colony and hasattr(self.colony, "add_requeen_strength"):
1807 self.colony.add_requeen_strength(self.rq_queen_strength)
1808 return True
1809 except Exception:
1810 self.add_to_error_list(f"Invalid rqqueenstrength: {value}")
1811 return False
1812 if name == "rqonce":
1813 self.rq_once = 0 if parse_bool(value) else 1
1814 return True
1816 # Treatment parameters
1817 if name == "vttreatmentduration":
1818 try:
1819 self.vt_treatment_duration = int(value)
1820 return True
1821 except Exception:
1822 self.add_to_error_list(f"Invalid vttreatmentduration: {value}")
1823 return False
1824 if name == "vtmortality":
1825 try:
1826 self.vt_mortality = int(value)
1827 return True
1828 except Exception:
1829 self.add_to_error_list(f"Invalid vtmortality: {value}")
1830 return False
1831 if name == "vttreatmentstart":
1832 dt = parse_date(value)
1833 if dt:
1834 self.vt_treatment_start = dt
1835 return True
1836 self.add_to_error_list(f"Invalid vttreatmentstart: {value}")
1837 return False
1838 if name == "vtenable":
1839 if self.colony and hasattr(self.colony, "set_vt_enable"):
1840 self.colony.set_vt_enable(parse_bool(value))
1841 return True
1842 return False
1844 # Add more explicit parameter handling for other classes (nutrient contamination, cold storage, etc.) as needed
1845 # Example: cold storage
1846 if name == "coldstoragestart":
1847 dt = parse_date(value)
1848 if dt and hasattr(self, "cold_storage_simulator"):
1849 self.cold_storage_simulator.set_start_date(dt)
1850 return True
1851 if name == "coldstorageend":
1852 dt = parse_date(value)
1853 if dt and hasattr(self, "cold_storage_simulator"):
1854 self.cold_storage_simulator.set_end_date(dt)
1855 return True
1856 if name == "coldstorageenable":
1857 if hasattr(self, "cold_storage_simulator"):
1858 self.cold_storage_simulator.set_enabled(parse_bool(value))
1859 return True
1861 # Fallback: unknown parameter
1862 self.add_to_error_list(f"Unknown parameter: {param_name}")
1863 return False
1865 def _generate_initial_conditions_row(self):
1866 """
1867 Generate the Initial row that shows exact initial conditions.
1868 Matches C++ logic from session.cpp lines 418-475.
1869 This row has date="Initial" and shows the colony state before any simulation days.
1870 """
1871 if not self.colony:
1872 return "Initial 0 0 0 0 0 0 0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 0 0 0.0 0.0 0 0.0 0.0 0.0 0.0 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 No"
1874 # Generate Initial row using exact C++ field order and formatting
1875 initial_data = [
1876 "Initial ", # "%s" - Date field = "Initial" (padded like C++)
1877 "%6d" % self.colony.get_colony_size(), # "%6d" - Colony size
1878 "%8d"
1879 % self.colony.get_adult_drones(), # "%8d" - Adult Drones (Dadl.GetQuantity())
1880 "%8d"
1881 % self.colony.get_adult_workers(), # "%8d" - Adult Workers (Wadl.GetQuantity())
1882 "%8d"
1883 % self.colony.get_foragers(), # "%8d" - Foragers (foragers.GetQuantity())
1884 "%8d"
1885 % self.colony.get_active_foragers(), # "%8d" - Active Foragers (foragers.GetActiveQuantity())
1886 "%7d"
1887 % self.colony.get_drone_brood(), # "%7d" - Drone Brood (CapDrn.GetQuantity())
1888 "%6d"
1889 % self.colony.get_worker_brood(), # "%6d" - Worker Brood (CapWkr.GetQuantity())
1890 "%6d"
1891 % self.colony.get_drone_larvae(), # "%6d" - Drone Larvae (Dlarv.GetQuantity())
1892 "%6d"
1893 % self.colony.get_worker_larvae(), # "%6d" - Worker Larvae (Wlarv.GetQuantity())
1894 "%6d"
1895 % self.colony.get_drone_eggs(), # "%6d" - Drone Eggs (Deggs.GetQuantity())
1896 "%6d"
1897 % self.colony.get_worker_eggs(), # "%6d" - Worker Eggs (Weggs.GetQuantity())
1898 "%6d"
1899 % self.colony.get_total_eggs_laid_today(), # "%6d" - Total Eggs (GetEggsToday())
1900 "%7.2f" % 0.0, # "%7.2f" - DD (GetDDToday() - 0 for Initial)
1901 "%6.2f" % 0.0, # "%6.2f" - L (GetLToday() - 0 for Initial)
1902 "%6.2f" % 0.0, # "%6.2f" - N (GetNToday() - 0 for Initial)
1903 "%8.2f" % 0.0, # "%8.2f" - P (GetPToday() - 0 for Initial)
1904 "%7.2f" % 0.0, # "%7.2f" - dd (GetddToday() - 0 for Initial)
1905 "%6.2f" % 0.0, # "%6.2f" - l (GetlToday() - 0 for Initial)
1906 "%8.2f" % 0.0, # "%8.2f" - n (GetnToday() - 0 for Initial)
1907 "%6.2f"
1908 % self.colony.get_free_mites(), # "%6.2f" - Free Mites (RunMite.GetTotal())
1909 "%6.2f"
1910 % self.colony.get_drone_brood_mites(), # "%6.2f" - DBrood Mites (CapDrn.GetMiteCount())
1911 "%6.2f"
1912 % self.colony.get_worker_brood_mites(), # "%6.2f" - WBrood Mites (CapWkr.GetMiteCount())
1913 "%6.2f"
1914 % self.colony.get_mites_per_drone_brood(), # "%6.2f" - DMite/Cell (CapDrn.GetMitesPerCell())
1915 "%6.2f"
1916 % self.colony.get_mites_per_worker_brood(), # "%6.2f" - WMite/Cell (CapWkr.GetMitesPerCell())
1917 "%6.0f" % 0, # "%6.0f" - Mites Dying (0 for Initial)
1918 "%6.0f" % 0.0, # "%6.0f" - Prop Mites Dying (0.0 for Initial)
1919 "%8.1f"
1920 % 0.0, # "%8.1f" - Colony Pollen (0.0 for Initial - matches C++ logic)
1921 "%7.4f" % 0.0, # "%7.4f" - Conc Pollen Pest (0.0 for Initial)
1922 "%8.1f"
1923 % 0.0, # "%8.1f" - Colony Nectar (0.0 for Initial - matches C++ logic)
1924 "%7.4f" % 0.0, # "%7.4f" - Conc Nectar Pest (0.0 for Initial)
1925 "%6d" % 0, # "%6d" - Dead DLarv (0 for Initial)
1926 "%6d" % 0, # "%6d" - Dead WLarv (0 for Initial)
1927 "%6d" % 0, # "%6d" - Dead DAdlt (0 for Initial)
1928 "%6d" % 0, # "%6d" - Dead WAdlt (0 for Initial)
1929 "%6d" % 0, # "%6d" - Dead Foragers (0 for Initial)
1930 "%8.3f"
1931 % self.colony.get_queen_strength(), # "%8.3f" - Queen Strength (queen.GetQueenStrength())
1932 "%8.3f" % 0.0, # "%8.3f" - Ave Temp (0.0 for Initial - no weather yet)
1933 "%6.3f" % 0.0, # "%6.3f" - Rain (0.0 for Initial)
1934 "%8.3f" % 0.0, # "%8.3f" - Min Temp (0.0 for Initial)
1935 "%8.3f" % 0.0, # "%8.3f" - Max Temp (0.0 for Initial)
1936 "%8.2f"
1937 % 0.0, # "%8.2f" - Daylight Hours (0.0 for Initial) - KEY FORMATTING
1938 "%8.2f" % 0.0, # "%8.2f" - Activity Ratio (0.0 for Initial)
1939 "No", # "%s" - Forage Day ("No" for Initial)
1940 ]
1942 return " ".join(initial_data)
1944 def initialize_simulation(self):
1945 self.results_text.clear()
1946 self.results_header.clear()
1947 self.results_file_header.clear()
1948 self.inc_immigrating_mites = 0
1949 if self.colony:
1950 self.colony.initialize_colony()
1951 self.colony.set_mite_pct_resistance(self.init_mite_pct_resistant)
1953 # Transfer VT enable flag from session to colony
1954 if hasattr(self, "vt_enable"):
1955 self.colony.set_vt_enable(self.vt_enable)
1957 # PYTHON-SPECIFIC EXTENSION: Create VT treatment item from individual parameters
1958 # This functionality is MISSING from the C++ implementation!
1959 # The C++ code stores VT parameters but never converts them into a treatment item.
1960 # This is a bug fix/enhancement that the Python port provides.
1961 if (
1962 hasattr(self, "vt_enable")
1963 and self.vt_enable
1964 and hasattr(self, "vt_treatment_start")
1965 and self.vt_treatment_start
1966 and hasattr(self, "vt_treatment_duration")
1967 and self.vt_treatment_duration > 0
1968 and hasattr(self, "vt_mortality")
1969 and self.vt_mortality > 0
1970 ):
1972 # Create the VT treatment item from the individual VT parameters
1973 # This bridges the gap between parameter storage and actual treatment application
1974 # Use the init_mite_pct_resistant as the resistance percentage for VT treatment
1975 # This matches the expected behavior where VT treatment affects mites based on resistance
1976 vt_treatment = MiteTreatmentItem(
1977 start_time=self.vt_treatment_start,
1978 duration=self.vt_treatment_duration,
1979 pct_mortality=float(self.vt_mortality),
1980 pct_resistant=float(self.init_mite_pct_resistant),
1981 )
1983 # Add the VT treatment to the colony's mite treatment info
1984 self.colony.m_mite_treatment_info.add_item(vt_treatment)
1986 if self.is_error_reporting_enabled():
1987 self.add_to_info_list(
1988 f"VT Treatment created: Start={self.vt_treatment_start.strftime('%m/%d/%Y')}, Duration={self.vt_treatment_duration} days, Mortality={self.vt_mortality}%, Resistance={self.init_mite_pct_resistant}%"
1989 )
1991 self.cum_immigrating_mites = 0
1992 self.first_result_entry = True