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

1"""BeePop+ Session Management Module. 

2 

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. 

7 

8The VarroaPopSession class is a Python port of the C++ CVarroaPopSession class 

9and maintains compatibility with the original API while adding Python-specific 

10enhancements. 

11 

12Architecture: 

13 VarroaPopSession acts as the main controller that coordinates: 

14 

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 

36 

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 

44 

45""" 

46 

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 

54 

55 

56class VarroaPopSession: 

57 """Central session manager for BeePop+ simulations. 

58 

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. 

62 

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. 

66 

67 """ 

68 

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 

120 

121 def __init__(self): 

122 """Initialize a new VarroaPopSession. 

123 

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. 

127 

128 The session is initialized with conservative default values for all 

129 parameters and empty data structures for results collection. 

130 

131 Raises: 

132 RuntimeError: If colony or weather initialization fails. 

133 

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 = "" 

199 

200 # Initialize main objects (matching C++ session constructor) 

201 # Create the colony object (equivalent to CColony theColony) 

202 self.colony = Colony(session=self) 

203 

204 # Set session reference in colony (matching theColony.SetSession(this)) 

205 if hasattr(self.colony, "set_session"): 

206 self.colony.set_session(self) 

207 

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 ) 

215 

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 

220 

221 def get_weather(self): 

222 """Get the weather events object (equivalent to C++ GetWeather()).""" 

223 return self.weather 

224 

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) 

230 

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 

236 

237 def is_weather_loaded(self): 

238 """Check if weather data has been loaded.""" 

239 return self.weather_loaded 

240 

241 def set_weather_loaded(self, load_complete): 

242 """Set the weather loaded status.""" 

243 self.weather_loaded = load_complete 

244 

245 def is_show_warnings(self): 

246 """Check if warnings should be shown.""" 

247 return self.show_warnings 

248 

249 def set_show_warnings(self, warn): 

250 """Set whether warnings should be shown.""" 

251 self.show_warnings = warn 

252 

253 # Error/Status list access 

254 def clear_error_list(self): 

255 self.error_list.clear() 

256 

257 def clear_info_list(self): 

258 self.information_list.clear() 

259 

260 def add_to_error_list(self, err_stg): 

261 self.error_list.append(err_stg) 

262 

263 def add_to_info_list(self, info_stg): 

264 self.information_list.append(info_stg) 

265 

266 def get_error_list(self): 

267 return self.error_list 

268 

269 def get_info_list(self): 

270 return self.information_list 

271 

272 def is_error_reporting_enabled(self): 

273 return self._enable_error_reporting 

274 

275 def is_info_reporting_enabled(self): 

276 return self._enable_info_reporting 

277 

278 def enable_error_reporting(self, enable): 

279 self._enable_error_reporting = bool(enable) 

280 

281 def enable_info_reporting(self, enable): 

282 self._enable_info_reporting = bool(enable) 

283 

284 # Simulation Operations 

285 def get_sim_start(self): 

286 return self.sim_start_time 

287 

288 def get_sim_end(self): 

289 return self.sim_end_time 

290 

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 

295 

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 

300 

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 

305 

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 

310 

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 

315 

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 ) 

323 

324 def is_simulation_complete(self): 

325 return self.simulation_complete 

326 

327 def are_results_ready(self): 

328 return self.results_ready 

329 

330 def get_results_length(self): 

331 return len(self.results_text) 

332 

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) 

339 

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. 

345 

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. 

349 

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 = [] 

355 

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 

368 

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 

377 

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 

384 

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 

394 

395 return consistent 

396 

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 

407 

408 # Check date consistency before starting simulation (matches C++ behavior) 

409 if not self.check_date_consistency(self.show_warnings): 

410 return 

411 

412 # Set results frequency 

413 res_freq = 7 if self.disp_weekly_data else 1 

414 

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" 

421 

422 # Initialize results headers 

423 self.set_default_headers() 

424 self.results_text.clear() 

425 self.results_file_text.clear() 

426 

427 # Add header rows (simplified) 

428 self.results_text.append(" ".join(self.results_header)) 

429 

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) 

433 

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 

439 

440 # Get first event from weather 

441 event = self.weather.get_day_event(sim_start) 

442 

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 ) 

457 

458 # Update bees 

459 self.colony.update_bees(event, day_count) 

460 

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 

472 

473 # Update mites 

474 self.colony.update_mites(event, day_count) 

475 

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) 

484 

485 # Pending events 

486 self.colony.do_pending_events(event, day_count) 

487 

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)) 

545 

546 if day_count % res_freq == 0: 

547 self.colony.set_start_sample_period() 

548 

549 day_count += 1 

550 if getattr(event, "is_forage_day", False): 

551 tot_foraging_days += 1 

552 event = self.weather.get_next_event() 

553 

554 self.results_ready = True 

555 self.simulation_complete = True 

556 self.colony.clear() 

557 self.simulation_complete = False 

558 

559 # Immigration Operations 

560 def set_immigration_type(self, im_type): 

561 self.immigration_type = im_type 

562 

563 def get_immigration_type(self): 

564 return self.immigration_type 

565 

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 

569 

570 def get_num_immigration_mites(self): 

571 return self.tot_immigrating_mites 

572 

573 def set_immigration_start(self, start): 

574 self.immigration_start_date = start 

575 

576 def get_immigration_start(self): 

577 return self.immigration_start_date 

578 

579 def set_immigration_end(self, end): 

580 self.immigration_end_date = end 

581 

582 def get_immigration_end(self): 

583 return self.immigration_end_date 

584 

585 def set_immigration_enabled(self, enabled): 

586 self.immigration_enabled = enabled 

587 

588 def is_immigration_enabled(self): 

589 return self.immigration_enabled 

590 

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 ) 

597 

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. 

602 

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 

607 

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. 

614 

615 The immigration functions are: 

616 

617 Cosine -> f(x) = 1.188395*cos(x) 

618 

619 Sine -> f(x) = 1.57078*sin(PI*x) 

620 

621 Tangent -> f(x) = 2.648784*tan(1.5*x) 

622 

623 Exponential -> f(x) = (1.0/(e-2))*(exp(1 - (x)) - 1.0) 

624 

625 Logarithmic -> f(x) = -1.0*log(x) day #2 and on 

626 

627 Polynomial -> f(x) = 3.0*(x) - 1.5*(x*x) 

628 

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. 

632 

633 Mites only immigrate on foraging days. 

634 

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 """ 

640 

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} 

653 

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 

657 

658 # Set cumulative immigration to 0 on first day 

659 if sim_day_today == sim_day_im_start: 

660 self.cum_immigrating_mites = 0 

661 

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 

668 

669 immigration_type = str(self.immigration_type).upper() 

670 total_mites = 0.0 

671 A = self.get_num_immigration_mites() # Total mites to distribute 

672 

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 

694 

695 if total_mites < 0.0: 

696 total_mites = 0.0 

697 

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} 

702 

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 """ 

714 

715 name = param_name.strip().lower() 

716 value = param_value.strip() 

717 

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 

722 

723 def parse_bool(val): 

724 return str(val).lower() in ("1", "true", "yes") 

725 

726 def parse_date(val): 

727 try: 

728 return datetime.datetime.strptime(val, "%m/%d/%Y") 

729 except Exception: 

730 return None 

731 

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 

756 

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 

838 

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) 

860 

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 

1018 

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 

1148 

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 

1190 

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 

1208 

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 

1258 

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 

1330 

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 

1448 

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 

1482 

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 

1500 

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 

1524 

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 

1638 

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 

1708 

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 

1717 

1718 if value.strip().lower() == "clear": 

1719 self.colony.m_mite_treatment_info.clear_all() 

1720 return True 

1721 

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 

1736 

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 

1742 

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 

1749 

1750 self.colony.m_mite_treatment_info.add_item_by_values( 

1751 start_date, 

1752 duration, 

1753 pct_mortality, 

1754 ) 

1755 return True 

1756 

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 

1795 

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 

1836 

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 

1853 

1854 # Fallback: unknown parameter 

1855 self.add_to_error_list(f"Unknown parameter: {param_name}") 

1856 return False 

1857 

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" 

1866 

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 ] 

1934 

1935 return " ".join(initial_data) 

1936 

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() 

1947 

1948 # Transfer VT enable flag from session to colony 

1949 if hasattr(self, "vt_enable"): 

1950 self.colony.set_vt_enable(self.vt_enable) 

1951 

1952 self.cum_immigrating_mites = 0 

1953 self.first_result_entry = True