test_simulation.py 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. import datetime
  2. import time
  3. from synergine2.config import Config
  4. from synergine2.cycle import CycleManager
  5. from synergine2.processing import ProcessManager
  6. from synergine2.share import shared
  7. from synergine2.simulation import Simulation
  8. from synergine2.simulation import Subjects
  9. from synergine2.simulation import SubjectBehaviour
  10. from synergine2.simulation import SubjectMechanism
  11. from synergine2.simulation import Subject
  12. from synergine2.simulation import SimulationMechanism
  13. from synergine2.simulation import SimulationBehaviour
  14. from tests import BaseTest
  15. from freezegun import freeze_time
  16. config = Config()
  17. class MySubjectMechanism(SubjectMechanism):
  18. def run(self):
  19. return {'foo': 42}
  20. class MySimulationMechanism(SimulationMechanism):
  21. def run(self, process_number: int=None, process_count: int=None):
  22. return {'foo': 42}
  23. class MySubjectBehaviour(SubjectBehaviour):
  24. use = [MySubjectMechanism]
  25. def run(self, data):
  26. return {'bar': data[MySubjectMechanism]['foo'] + 100}
  27. class MySimulationBehaviour(SimulationBehaviour):
  28. use = [MySimulationMechanism]
  29. def run(self, data):
  30. return {'bar': data[MySimulationMechanism]['foo'] + 100}
  31. class MySimulation(Simulation):
  32. behaviours_classes = [MySimulationBehaviour]
  33. class MyCycledSubjectBehaviour(MySubjectBehaviour):
  34. @property
  35. def cycle_frequency(self):
  36. return 2
  37. class MyCycledSimulationBehaviour(MySimulationBehaviour):
  38. @property
  39. def cycle_frequency(self):
  40. return 2
  41. class MyTimedSubjectBehaviour(MySubjectBehaviour):
  42. @property
  43. def seconds_frequency(self):
  44. return 1.0
  45. def run(self, data):
  46. self.last_execution_time = time.time()
  47. return super().run(data)
  48. class MyTimedSimulationBehaviour(MySimulationBehaviour):
  49. @property
  50. def seconds_frequency(self):
  51. return 1.0
  52. def run(self, data):
  53. self.last_execution_time = time.time()
  54. return super().run(data)
  55. class TestBehaviours(BaseTest):
  56. def test_subject_behaviour_produce_data(
  57. self,
  58. do_nothing_process_manager: ProcessManager,
  59. ):
  60. shared.reset()
  61. class MySubject(Subject):
  62. behaviours_classes = [MySubjectBehaviour]
  63. simulation = Simulation(config)
  64. my_subject = MySubject(config, simulation)
  65. subjects = Subjects(simulation=simulation)
  66. subjects.append(my_subject)
  67. simulation.subjects = subjects
  68. cycle_manager = CycleManager(
  69. config,
  70. simulation=simulation,
  71. process_manager=do_nothing_process_manager,
  72. )
  73. results_by_subjects = cycle_manager._job_subjects(worker_id=0, process_count=1)
  74. assert results_by_subjects
  75. assert id(my_subject) in results_by_subjects
  76. assert MySubjectBehaviour in results_by_subjects[id(my_subject)]
  77. assert 'bar' in results_by_subjects[id(my_subject)][MySubjectBehaviour]
  78. assert 142 == results_by_subjects[id(my_subject)][MySubjectBehaviour]['bar']
  79. def test_simulation_behaviour_produce_data(
  80. self,
  81. do_nothing_process_manager: ProcessManager,
  82. ):
  83. shared.reset()
  84. simulation = MySimulation(config)
  85. subjects = Subjects(simulation=simulation)
  86. simulation.subjects = subjects
  87. cycle_manager = CycleManager(
  88. config,
  89. simulation=simulation,
  90. process_manager=do_nothing_process_manager,
  91. )
  92. data = cycle_manager._job_simulation(worker_id=0, process_count=1)
  93. assert data
  94. assert MySimulationBehaviour in data
  95. assert 'bar' in data[MySimulationBehaviour]
  96. assert 142 == data[MySimulationBehaviour]['bar']
  97. def test_subject_behaviour_cycle_frequency(
  98. self,
  99. do_nothing_process_manager: ProcessManager,
  100. ):
  101. shared.reset()
  102. class MySubject(Subject):
  103. behaviours_classes = [MyCycledSubjectBehaviour]
  104. simulation = Simulation(config)
  105. my_subject = MySubject(config, simulation)
  106. subjects = Subjects(simulation=simulation)
  107. subjects.append(my_subject)
  108. simulation.subjects = subjects
  109. cycle_manager = CycleManager(
  110. config,
  111. simulation=simulation,
  112. process_manager=do_nothing_process_manager,
  113. )
  114. # Cycle 0: behaviour IS executed
  115. cycle_manager.current_cycle = 0
  116. results_by_subjects = cycle_manager._job_subjects(worker_id=0, process_count=1)
  117. assert results_by_subjects
  118. # Cycle 1: behaviour IS NOT executed
  119. cycle_manager.current_cycle = 1
  120. results_by_subjects = cycle_manager._job_subjects(worker_id=0, process_count=1)
  121. assert not results_by_subjects
  122. def test_subject_behaviour_seconds_frequency(
  123. self,
  124. do_nothing_process_manager: ProcessManager,
  125. ):
  126. shared.reset()
  127. class MySubject(Subject):
  128. behaviours_classes = [MyTimedSubjectBehaviour]
  129. simulation = Simulation(config)
  130. my_subject = MySubject(config, simulation)
  131. subjects = Subjects(simulation=simulation)
  132. subjects.append(my_subject)
  133. simulation.subjects = subjects
  134. cycle_manager = CycleManager(
  135. config,
  136. simulation=simulation,
  137. process_manager=do_nothing_process_manager,
  138. )
  139. # Thirst time, behaviour IS executed
  140. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0)):
  141. data = cycle_manager._job_subjects(worker_id=0, process_count=1)
  142. assert data
  143. assert id(my_subject) in data
  144. assert data[id(my_subject)]
  145. # Less second after: NOT executed
  146. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 500000)):
  147. data = cycle_manager._job_subjects(worker_id=0, process_count=1)
  148. assert not data
  149. # Less second after: NOT executed
  150. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 700000)):
  151. data = cycle_manager._job_subjects(worker_id=0, process_count=1)
  152. assert not data
  153. # Less second after: IS executed
  154. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 1, 500000)):
  155. data = cycle_manager._job_subjects(worker_id=0, process_count=1)
  156. assert data
  157. def test_simulation_behaviour_cycle_frequency(
  158. self,
  159. do_nothing_process_manager: ProcessManager,
  160. ):
  161. shared.reset()
  162. class MyCycledSimulation(Simulation):
  163. behaviours_classes = [MyCycledSimulationBehaviour]
  164. simulation = MyCycledSimulation(config)
  165. subjects = Subjects(simulation=simulation)
  166. simulation.subjects = subjects
  167. cycle_manager = CycleManager(
  168. config,
  169. simulation=simulation,
  170. process_manager=do_nothing_process_manager,
  171. )
  172. # Cycle 0: behaviour IS executed
  173. cycle_manager.current_cycle = 0
  174. data = cycle_manager._job_simulation(worker_id=0, process_count=1)
  175. assert data
  176. # Cycle 1: behaviour IS NOT executed
  177. cycle_manager.current_cycle = 1
  178. data = cycle_manager._job_simulation(worker_id=0, process_count=1)
  179. assert not data
  180. def test_simulation_behaviour_seconds_frequency(
  181. self,
  182. do_nothing_process_manager: ProcessManager,
  183. ):
  184. shared.reset()
  185. class MyTimedSimulation(Simulation):
  186. behaviours_classes = [MyTimedSimulationBehaviour]
  187. simulation = MyTimedSimulation(config)
  188. subjects = Subjects(simulation=simulation)
  189. simulation.subjects = subjects
  190. cycle_manager = CycleManager(
  191. config,
  192. simulation=simulation,
  193. process_manager=do_nothing_process_manager,
  194. )
  195. # Thirst time, behaviour IS executed
  196. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0)):
  197. data = cycle_manager._job_simulation(worker_id=0, process_count=1)
  198. assert data
  199. # Less second after: NOT executed
  200. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 500000)):
  201. data = cycle_manager._job_simulation(worker_id=0, process_count=1)
  202. assert not data
  203. # Less second after: NOT executed
  204. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 700000)):
  205. data = cycle_manager._job_simulation(worker_id=0, process_count=1)
  206. assert not data
  207. # More second after: IS executed
  208. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 1, 500000)):
  209. data = cycle_manager._job_simulation(worker_id=0, process_count=1)
  210. assert data
  211. def test_subject_behavior_not_called_if_no_more_subjects(
  212. self,
  213. do_nothing_process_manager: ProcessManager,
  214. ):
  215. shared.reset()
  216. class MySubject(Subject):
  217. behaviours_classes = [MySubjectBehaviour]
  218. simulation = Simulation(config)
  219. my_subject = MySubject(config, simulation)
  220. subjects = Subjects(simulation=simulation)
  221. subjects.append(my_subject)
  222. simulation.subjects = subjects
  223. cycle_manager = CycleManager(
  224. config,
  225. simulation=simulation,
  226. process_manager=do_nothing_process_manager,
  227. )
  228. results_by_subjects = cycle_manager._job_subjects(worker_id=0, process_count=1)
  229. assert results_by_subjects
  230. assert id(my_subject) in results_by_subjects
  231. assert results_by_subjects[id(my_subject)]
  232. # If we remove subject, no more data generated
  233. subjects.remove(my_subject)
  234. results_by_subjects = cycle_manager._job_subjects(worker_id=0, process_count=1)
  235. assert not results_by_subjects
  236. class TestMechanisms(BaseTest):
  237. def test_mechanism_called_once_for_multiple_subject_behaviors(
  238. self,
  239. do_nothing_process_manager: ProcessManager,
  240. ):
  241. shared.reset()
  242. global called
  243. called = 0
  244. class MySubjectMechanism(SubjectMechanism):
  245. def run(self):
  246. global called
  247. called += 1
  248. return {'foo': 42}
  249. class MySubjectBehaviour1(SubjectBehaviour):
  250. use = [MySubjectMechanism]
  251. def run(self, data):
  252. return {'bar': data[MySubjectMechanism]['foo'] + 100}
  253. class MySubjectBehaviour2(SubjectBehaviour):
  254. use = [MySubjectMechanism]
  255. def run(self, data):
  256. return {'bar': data[MySubjectMechanism]['foo'] + 100}
  257. class MySubject(Subject):
  258. behaviours_classes = [MySubjectBehaviour1, MySubjectBehaviour2]
  259. simulation = Simulation(config)
  260. my_subject = MySubject(config, simulation)
  261. subjects = Subjects(simulation=simulation)
  262. subjects.append(my_subject)
  263. simulation.subjects = subjects
  264. cycle_manager = CycleManager(
  265. config,
  266. simulation=simulation,
  267. process_manager=do_nothing_process_manager,
  268. )
  269. cycle_manager._job_subjects(worker_id=0, process_count=1)
  270. assert called == 1
  271. def test_mechanism_called_once_for_multiple_simulation_behaviors(
  272. self,
  273. do_nothing_process_manager: ProcessManager,
  274. ):
  275. shared.reset()
  276. global called
  277. called = 0
  278. class MySimulationMechanism(SimulationMechanism):
  279. def run(self, process_number: int = None, process_count: int = None):
  280. global called
  281. called += 1
  282. return {'foo': 42}
  283. class MySimulationBehaviour1(SimulationBehaviour):
  284. use = [MySimulationMechanism]
  285. def run(self, data):
  286. return {'bar': data[MySimulationMechanism]['foo'] + 100}
  287. class MySimulationBehaviour2(SimulationBehaviour):
  288. use = [MySimulationMechanism]
  289. def run(self, data):
  290. return {'bar': data[MySimulationMechanism]['foo'] + 100}
  291. class MySimulation(Simulation):
  292. behaviours_classes = [MySimulationBehaviour1, MySimulationBehaviour2]
  293. simulation = MySimulation(config)
  294. subjects = Subjects(simulation=simulation)
  295. simulation.subjects = subjects
  296. cycle_manager = CycleManager(
  297. config,
  298. simulation=simulation,
  299. process_manager=do_nothing_process_manager,
  300. )
  301. cycle_manager._job_simulation(worker_id=0, process_count=1)
  302. assert called == 1
  303. def test_mechanism_not_called_if_no_subject_behavior(
  304. self,
  305. do_nothing_process_manager: ProcessManager,
  306. ):
  307. shared.reset()
  308. global called
  309. called = 0
  310. class MySubjectMechanism(SubjectMechanism):
  311. def run(self):
  312. global called
  313. called += 1
  314. return {'foo': 42}
  315. class MySubject(Subject):
  316. behaviours_classes = []
  317. simulation = Simulation(config)
  318. my_subject = MySubject(config, simulation)
  319. subjects = Subjects(simulation=simulation)
  320. subjects.append(my_subject)
  321. simulation.subjects = subjects
  322. cycle_manager = CycleManager(
  323. config,
  324. simulation=simulation,
  325. process_manager=do_nothing_process_manager,
  326. )
  327. cycle_manager._job_subjects(worker_id=0, process_count=1)
  328. assert called == 0
  329. def test_mechanism_not_called_if_no_simulation_behavior(
  330. self,
  331. do_nothing_process_manager: ProcessManager,
  332. ):
  333. shared.reset()
  334. global called
  335. called = 0
  336. class MySimulationMechanism(SimulationMechanism):
  337. def run(self, process_number: int = None, process_count: int = None):
  338. global called
  339. called += 1
  340. return {'foo': 42}
  341. class MySimulation(Simulation):
  342. pass
  343. simulation = MySimulation(config)
  344. subjects = Subjects(simulation=simulation)
  345. simulation.subjects = subjects
  346. cycle_manager = CycleManager(
  347. config,
  348. simulation=simulation,
  349. process_manager=do_nothing_process_manager,
  350. )
  351. cycle_manager._job_simulation(worker_id=0, process_count=1)
  352. assert called == 0
  353. def test_mechanism_not_called_if_subject_behavior_cycled_not_active_yet(
  354. self,
  355. do_nothing_process_manager: ProcessManager,
  356. ):
  357. shared.reset()
  358. global called
  359. called = 0
  360. class MySubjectMechanism(SubjectMechanism):
  361. def run(self):
  362. global called
  363. called += 1
  364. return {'foo': 42}
  365. class MySubjectBehaviour1(SubjectBehaviour):
  366. use = [MySubjectMechanism]
  367. @property
  368. def cycle_frequency(self):
  369. return 2
  370. def run(self, data):
  371. return {'bar': data[MySubjectMechanism]['foo'] + 100}
  372. class MySubject(Subject):
  373. behaviours_classes = [MySubjectBehaviour1]
  374. simulation = Simulation(config)
  375. my_subject = MySubject(config, simulation)
  376. subjects = Subjects(simulation=simulation)
  377. subjects.append(my_subject)
  378. simulation.subjects = subjects
  379. cycle_manager = CycleManager(
  380. config,
  381. simulation=simulation,
  382. process_manager=do_nothing_process_manager,
  383. )
  384. cycle_manager.current_cycle = 0
  385. cycle_manager._job_subjects(worker_id=0, process_count=1)
  386. assert called == 1
  387. cycle_manager.current_cycle = 1
  388. cycle_manager._job_subjects(worker_id=0, process_count=1)
  389. assert called == 1
  390. cycle_manager.current_cycle = 2
  391. cycle_manager._job_subjects(worker_id=0, process_count=1)
  392. assert called == 2
  393. cycle_manager.current_cycle = 3
  394. cycle_manager._job_subjects(worker_id=0, process_count=1)
  395. assert called == 2
  396. def test_mechanism_not_called_if_simulation_behavior_cycled_not_active_yet(
  397. self,
  398. do_nothing_process_manager: ProcessManager,
  399. ):
  400. shared.reset()
  401. global called
  402. called = 0
  403. class MySimulationMechanism(SimulationMechanism):
  404. def run(self, process_number: int = None, process_count: int = None):
  405. global called
  406. called += 1
  407. return {'foo': 42}
  408. class MySimulationBehaviour1(SimulationBehaviour):
  409. use = [MySimulationMechanism]
  410. @property
  411. def cycle_frequency(self):
  412. return 2
  413. def run(self, data):
  414. return {'bar': data[MySimulationMechanism]['foo'] + 100}
  415. class MySimulation(Simulation):
  416. behaviours_classes = [MySimulationBehaviour1]
  417. simulation = MySimulation(config)
  418. subjects = Subjects(simulation=simulation)
  419. simulation.subjects = subjects
  420. cycle_manager = CycleManager(
  421. config,
  422. simulation=simulation,
  423. process_manager=do_nothing_process_manager,
  424. )
  425. cycle_manager.current_cycle = 0
  426. cycle_manager._job_simulation(worker_id=0, process_count=1)
  427. assert called == 1
  428. cycle_manager.current_cycle = 1
  429. cycle_manager._job_simulation(worker_id=0, process_count=1)
  430. assert called == 1
  431. cycle_manager.current_cycle = 2
  432. cycle_manager._job_simulation(worker_id=0, process_count=1)
  433. assert called == 2
  434. cycle_manager.current_cycle = 3
  435. cycle_manager._job_simulation(worker_id=0, process_count=1)
  436. assert called == 2
  437. def test_mechanism_not_called_if_subject_behavior_timebase_not_active_yet(
  438. self,
  439. do_nothing_process_manager: ProcessManager,
  440. ):
  441. shared.reset()
  442. global called
  443. called = 0
  444. class MySubjectMechanism(SubjectMechanism):
  445. def run(self):
  446. global called
  447. called += 1
  448. return {'foo': 42}
  449. class MySubjectBehaviour1(SubjectBehaviour):
  450. use = [MySubjectMechanism]
  451. @property
  452. def seconds_frequency(self):
  453. return 1.0
  454. def run(self, data):
  455. self.last_execution_time = time.time()
  456. return {'bar': data[MySubjectMechanism]['foo'] + 100}
  457. class MySubject(Subject):
  458. behaviours_classes = [MySubjectBehaviour1]
  459. simulation = Simulation(config)
  460. my_subject = MySubject(config, simulation)
  461. subjects = Subjects(simulation=simulation)
  462. subjects.append(my_subject)
  463. simulation.subjects = subjects
  464. cycle_manager = CycleManager(
  465. config,
  466. simulation=simulation,
  467. process_manager=do_nothing_process_manager,
  468. )
  469. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0)):
  470. cycle_manager._job_subjects(worker_id=0, process_count=1)
  471. assert called == 1
  472. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 500000)):
  473. cycle_manager._job_subjects(worker_id=0, process_count=1)
  474. assert called == 1
  475. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 700000)):
  476. cycle_manager._job_subjects(worker_id=0, process_count=1)
  477. assert called == 1
  478. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 1, 500000)):
  479. cycle_manager._job_subjects(worker_id=0, process_count=1)
  480. assert called == 2
  481. def test_mechanism_not_called_if_simulation_behavior_timebase_not_active_yet(
  482. self,
  483. do_nothing_process_manager: ProcessManager,
  484. ):
  485. shared.reset()
  486. global called
  487. called = 0
  488. class MySimulationMechanism(SimulationMechanism):
  489. def run(self, process_number: int = None, process_count: int = None):
  490. global called
  491. called += 1
  492. return {'foo': 42}
  493. class MySimulationBehaviour1(SimulationBehaviour):
  494. use = [MySimulationMechanism]
  495. @property
  496. def seconds_frequency(self):
  497. return 1.0
  498. def run(self, data):
  499. self.last_execution_time = time.time()
  500. return {'bar': data[MySimulationMechanism]['foo'] + 100}
  501. class MySimulation(Simulation):
  502. behaviours_classes = [MySimulationBehaviour1]
  503. simulation = MySimulation(config)
  504. subjects = Subjects(simulation=simulation)
  505. simulation.subjects = subjects
  506. cycle_manager = CycleManager(
  507. config,
  508. simulation=simulation,
  509. process_manager=do_nothing_process_manager,
  510. )
  511. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 0)):
  512. cycle_manager._job_simulation(worker_id=0, process_count=1)
  513. assert called == 1
  514. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 500000)):
  515. cycle_manager._job_simulation(worker_id=0, process_count=1)
  516. assert called == 1
  517. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 0, 700000)):
  518. cycle_manager._job_simulation(worker_id=0, process_count=1)
  519. assert called == 1
  520. with freeze_time(datetime.datetime(2000, 12, 1, 0, 0, 1, 500000)):
  521. cycle_manager._job_simulation(worker_id=0, process_count=1)
  522. assert called == 2
  523. # TODO: Test Simulation mechanism parralelisation
  524. # TODO: Test behaviour actions generation