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