cycle.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. # coding: utf-8
  2. import multiprocessing
  3. import typing
  4. from synergine2.base import BaseObject
  5. from synergine2.config import Config
  6. from synergine2.exceptions import SynergineException
  7. from synergine2.log import SynergineLogger
  8. from synergine2.processing import ProcessManager
  9. from synergine2.share import shared
  10. from synergine2.simulation import Subject
  11. from synergine2.simulation import Simulation
  12. from synergine2.simulation import SubjectBehaviour
  13. from synergine2.simulation import SubjectMechanism
  14. from synergine2.simulation import Event
  15. from synergine2.utils import time_it
  16. JOB_TYPE_SUBJECTS = 0
  17. JOB_TYPE_SIMULATION = 1
  18. class CycleManager(BaseObject):
  19. def __init__(
  20. self,
  21. config: Config,
  22. logger: SynergineLogger,
  23. simulation: Simulation,
  24. process_manager: ProcessManager=None,
  25. ):
  26. # TODO: reproduire le mechanisme d'index de behaviour/etc pour simulation
  27. self.config = config
  28. self.logger = logger
  29. self.simulation = simulation
  30. self.current_cycle = -1
  31. self.first_cycle = True
  32. # TODO NOW: Les processes devront maintenir une liste des subjects qui sont nouveaux.ne connaissent pas
  33. # Attention a ce qu'in ne soient pas "expose" quand on créer ces subjects au sein du process.
  34. # Ces subjects ont vocation à adopter l'id du vrau subject tout de suite après leur instanciation
  35. if process_manager is None:
  36. process_manager = ProcessManager(
  37. config=config,
  38. # TODO: Changer de config de merde (core.use_x_cores)
  39. process_count=config.get('core', {}).get('use_x_cores', multiprocessing.cpu_count()),
  40. job=self.job,
  41. )
  42. self.process_manager = process_manager
  43. def job(self, worker_id: int, process_count: int, job_type: str) -> 'TODO':
  44. # ICI: (in process) on doit avoir:
  45. # La tranche x:y de sujets à traiter
  46. shared.refresh()
  47. if job_type == JOB_TYPE_SUBJECTS:
  48. return self._job_subjects(worker_id, process_count)
  49. if job_type == JOB_TYPE_SIMULATION:
  50. return self._job_simulation(worker_id, process_count)
  51. raise SynergineException('Unknown job type "{}"'.format(job_type))
  52. def _job_subjects(self, worker_id: int, process_count: int) -> typing.Dict[int, typing.Dict[str, typing.Any]]:
  53. # Determine list of process subject to work with
  54. subject_ids = shared.get('subject_ids')
  55. chunk_length, rest = divmod(len(subject_ids), process_count)
  56. from_ = chunk_length * worker_id
  57. to_ = from_ + chunk_length
  58. if worker_id + 1 == process_count:
  59. to_ += rest
  60. subject_ids_to_parse = subject_ids[from_:to_]
  61. # Build list of subjects for compute them
  62. subjects = []
  63. for subject_id in subject_ids_to_parse:
  64. subject = self.simulation.get_or_create_subject(subject_id)
  65. subjects.append(subject)
  66. results_by_subjects = self._subjects_computing(subjects)
  67. return results_by_subjects
  68. def _job_simulation(self, worker_id: int, process_count: int) -> typing.Dict[int, typing.Dict[str, typing.Any]]:
  69. self.logger.info('Simulation computing (worker {})'.format(worker_id))
  70. mechanisms = self.simulation.mechanisms.values()
  71. mechanisms_data = {}
  72. behaviours_data = {}
  73. self.logger.info('{} mechanisms to compute'.format(str(len(mechanisms))))
  74. if self.logger.is_debug:
  75. self.logger.debug('Mechanisms are: {}'.format(
  76. str([m.repr_debug() for m in mechanisms])
  77. ))
  78. for mechanism in mechanisms:
  79. mechanism_data = mechanism.run(
  80. process_number=worker_id,
  81. process_count=process_count,
  82. )
  83. if self.logger.is_debug:
  84. self.logger.debug('{} mechanism product data: {}'.format(
  85. type(mechanism).__name__,
  86. str(mechanism_data),
  87. ))
  88. mechanisms_data[mechanism.__class__] = mechanism_data
  89. behaviours = self.simulation.behaviours.values()
  90. self.logger.info('{} behaviours to compute'.format(str(len(behaviours))))
  91. if self.logger.is_debug:
  92. self.logger.debug('Behaviours are: {}'.format(
  93. str([b.repr_debug() for b in behaviours])
  94. ))
  95. for behaviour in behaviours:
  96. behaviour_data = behaviour.run(mechanisms_data) # TODO: Behaviours dependencies
  97. if self.logger.is_debug:
  98. self.logger.debug('{} behaviour produce data: {}'.format(
  99. type(behaviour).__name__,
  100. behaviour_data,
  101. ))
  102. if behaviour_data:
  103. behaviours_data[behaviour.__class__] = behaviour_data
  104. return behaviours_data
  105. def next(self) -> [Event]:
  106. if self.first_cycle:
  107. # To dispatch subjects add/removes, enable track on them
  108. self.simulation.subjects.track_changes = True
  109. self.first_cycle = False
  110. self.current_cycle += 1
  111. self.logger.info('Process cycle {}'.format(self.current_cycle))
  112. events = []
  113. shared.commit()
  114. # TODO: gestion des behaviours non parallelisables
  115. # TODO: Proposer des ordres d'execution
  116. with time_it() as elapsed_time:
  117. events.extend(self._get_subjects_events())
  118. print('Cycle subjects events duration: {}s'.format(elapsed_time.get_final_time()))
  119. with time_it() as elapsed_time:
  120. events.extend(self._get_simulation_events())
  121. print('Cycle simulation events duration: {}s'.format(elapsed_time.get_final_time()))
  122. self.logger.info('Cycle {} generate {} events'.format(
  123. str(self.current_cycle),
  124. str(len(events)),
  125. ))
  126. return events
  127. def _get_simulation_events(self) -> [Event]:
  128. events = []
  129. results = {}
  130. self.logger.info('Process simulation events')
  131. # TODO: Think about compute simulation cycle in workers
  132. results_by_processes = self.process_manager.make_them_work(JOB_TYPE_SIMULATION)
  133. for process_result in results_by_processes:
  134. for behaviour_class, behaviour_result in process_result.items():
  135. results[behaviour_class] = behaviour_class.merge_data(
  136. behaviour_result,
  137. results.get(behaviour_class),
  138. )
  139. self.logger.info('Simulation generate {} behaviours'.format(len(results)))
  140. # Make events
  141. for behaviour_class, behaviour_data in results.items():
  142. behaviour_events = self.simulation.behaviours[behaviour_class].action(behaviour_data)
  143. self.logger.info('{} behaviour generate {} events'.format(
  144. str(behaviour_class),
  145. str(len(behaviour_events)),
  146. ))
  147. if self.logger.is_debug:
  148. self.logger.debug('{} behaviour generated events: {}'.format(
  149. str(behaviour_class),
  150. str([e.repr_debug() for e in behaviour_events]),
  151. ))
  152. events.extend(behaviour_events)
  153. self.logger.info('Simulation behaviours generate {} events'.format(len(events)))
  154. return events
  155. def _get_subjects_events(self) -> [Event]:
  156. events = []
  157. results = {}
  158. self.logger.info('Process subjects events')
  159. results_by_processes = self.process_manager.make_them_work(JOB_TYPE_SUBJECTS)
  160. for process_results in results_by_processes:
  161. results.update(process_results)
  162. # Duplicate list to prevent conflicts with behaviours subjects manipulations
  163. for subject in self.simulation.subjects[:]:
  164. subject_behaviours_results = results.get(subject.id, {})
  165. if subject.behaviour_selector:
  166. # TODO: Looging
  167. subject_behaviours_results = subject.behaviour_selector.reduce_behaviours(dict(
  168. subject_behaviours_results,
  169. ))
  170. subject_behaviours = subject.behaviours
  171. for behaviour_class, behaviour_data in subject_behaviours_results.items():
  172. # TODO: Ajouter une etape de selection des actions a faire (genre neuronnal)
  173. # (genre se cacher et fuir son pas compatibles)
  174. behaviour_events = subject_behaviours[behaviour_class].action(behaviour_data)
  175. self.logger.info('{} behaviour for subject {} generate {} events'.format(
  176. str(behaviour_class.__name__),
  177. str(subject.id),
  178. str(len(behaviour_events)),
  179. ))
  180. if self.logger.is_debug:
  181. self.logger.debug('{} behaviour for subject {} generated events: {}'.format(
  182. str(behaviour_class.__name__),
  183. str(subject.id),
  184. str([e.repr_debug() for e in behaviour_events]),
  185. ))
  186. events.extend(behaviour_events)
  187. self.logger.info('Subjects behaviours generate {} events'.format(len(events)))
  188. return events
  189. def _subjects_computing(
  190. self,
  191. subjects,
  192. process_number=None,
  193. process_count=None,
  194. ) -> typing.Dict[int, typing.Dict[str, typing.Any]]:
  195. results = {}
  196. self.logger.info('Subjects computing: {} subjects to compute'.format(str(len(subjects))))
  197. for subject in subjects:
  198. mechanisms = subject.mechanisms.values()
  199. if mechanisms:
  200. self.logger.info('Subject {}: {} mechanisms'.format(
  201. str(subject.id),
  202. str(len(mechanisms)),
  203. ))
  204. if self.logger.is_debug:
  205. self.logger.info('Subject {}: mechanisms are: {}'.format(
  206. str(subject.id),
  207. str([m.repr_debug for m in mechanisms])
  208. ))
  209. mechanisms_data = {}
  210. behaviours_data = {}
  211. for mechanism in mechanisms:
  212. with time_it() as elapsed_time:
  213. mechanism_data = mechanism.run()
  214. if self.logger.is_debug:
  215. self.logger.debug('Subject {}: {} mechanisms produce data: {} in {}s'.format(
  216. str(subject.id),
  217. type(mechanism).__name__,
  218. str(mechanism_data),
  219. elapsed_time.get_final_time(),
  220. ))
  221. mechanisms_data[mechanism.__class__] = mechanism_data
  222. if mechanisms:
  223. if self.logger.is_debug:
  224. self.logger.info('Subject {}: mechanisms data are: {}'.format(
  225. str(subject.id),
  226. str(mechanisms_data),
  227. ))
  228. subject_behaviours = subject.behaviours
  229. if not subject_behaviours:
  230. break
  231. self.logger.info('Subject {}: have {} behaviours'.format(
  232. str(subject.id),
  233. str(len(subject_behaviours)),
  234. ))
  235. for behaviour in subject_behaviours.values():
  236. self.logger.info('Subject {}: run {} behaviour'.format(
  237. str(subject.id),
  238. str(type(behaviour)),
  239. ))
  240. # We identify behaviour data with it's class to be able to intersect it after subprocess data collect
  241. with time_it() as elapsed_time:
  242. behaviour_data = behaviour.run(mechanisms_data) # TODO: Behaviours dependencies
  243. if self.logger.is_debug:
  244. self.logger.debug('Subject {}: behaviour {} produce data: {} in {}s'.format(
  245. str(type(behaviour)),
  246. str(subject.id),
  247. str(behaviour_data),
  248. elapsed_time.get_final_time(),
  249. ))
  250. if behaviour_data:
  251. behaviours_data[behaviour.__class__] = behaviour_data
  252. results[subject.id] = behaviours_data
  253. return results
  254. def apply_actions(
  255. self,
  256. simulation_actions: [tuple]=None,
  257. subject_actions: [tuple]=None,
  258. ) -> [Event]:
  259. """
  260. TODO: bien specifier la forme des parametres
  261. simulation_actions = [(class, {'data': 'foo'})]
  262. subject_actions = [(subject_id, [(class, {'data': 'foo'}])]
  263. """
  264. simulation_actions = simulation_actions or []
  265. subject_actions = subject_actions or []
  266. events = []
  267. self.logger.info('Apply {} simulation_actions and {} subject_actions'.format(
  268. len(simulation_actions),
  269. len(subject_actions),
  270. ))
  271. for subject_id, behaviours_and_data in subject_actions:
  272. subject = self.simulation.subjects.index.get(subject_id)
  273. for behaviour_class, behaviour_data in behaviours_and_data:
  274. behaviour = behaviour_class(
  275. simulation=self.simulation,
  276. subject=subject,
  277. )
  278. self.logger.info('Apply {} behaviour on subject {}'.format(
  279. str(behaviour_class),
  280. str(subject_id),
  281. ))
  282. if self.logger.is_debug:
  283. self.logger.debug('{} behaviour data is {}'.format(
  284. str(behaviour_class),
  285. str(behaviour_data),
  286. ))
  287. behaviour_events = behaviour.action(behaviour_data)
  288. self.logger.info('{} events from behaviour {} from subject {}'.format(
  289. len(behaviour_events),
  290. str(behaviour_class),
  291. str(subject_id),
  292. ))
  293. if self.logger.is_debug:
  294. self.logger.debug('Events from behaviour {} from subject {} are: {}'.format(
  295. str(behaviour_class),
  296. str(subject_id),
  297. str([e.repr_debug() for e in behaviour_events])
  298. ))
  299. events.extend(behaviour_events)
  300. for behaviour_class, behaviour_data in simulation_actions:
  301. behaviour = behaviour_class(
  302. config=self.config,
  303. simulation=self.simulation,
  304. )
  305. self.logger.info('Apply {} simulation behaviour'.format(
  306. str(behaviour_class),
  307. ))
  308. behaviour_events = behaviour.action(behaviour_data)
  309. if self.logger.is_debug:
  310. self.logger.debug('Events from behaviour {} are: {}'.format(
  311. str(behaviour_class),
  312. str([e.repr_debug() for e in behaviour_events])
  313. ))
  314. events.extend(behaviour_events)
  315. self.logger.info('{} events generated'.format(len(events)))
  316. return events
  317. def stop(self) -> None:
  318. self.process_manager.terminate()