simulation.py 10.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. # coding: utf-8
  2. import collections
  3. import typing
  4. from synergine2.base import BaseObject
  5. from synergine2.config import Config
  6. from synergine2.share import shared
  7. from synergine2.utils import get_mechanisms_classes
  8. class Intention(object):
  9. pass
  10. class IntentionManager(object):
  11. intentions = shared.create(['{id}', 'intentions'], {}) # type: typing.Dict[typing.Type[Intention], Intention]
  12. def __init__(self):
  13. self._id = id(self)
  14. self.intentions = {}
  15. @property
  16. def id(self) -> int:
  17. return self._id
  18. def set(self, intention: Intention) -> None:
  19. self.intentions[type(intention)] = intention
  20. def get(self, intention_type: typing.Type[Intention]) -> Intention:
  21. # TODO: Raise specialised exception if KeyError
  22. return self.intentions[intention_type]
  23. class Subject(BaseObject):
  24. collections = []
  25. behaviours_classes = []
  26. behaviour_selector_class = None # type: typing.Type[SubjectBehaviourSelector]
  27. intention_manager_class = None # type: typing.Type[IntentionManager]
  28. def __init__(
  29. self,
  30. config: Config,
  31. simulation: 'Simulation',
  32. ):
  33. # TODO: Bannir les attribut de classe passé en reference ! Et meme virer les attr de classe tout court.
  34. self.collections = self.collections[:]
  35. self.config = config
  36. self._id = id(self) # We store object id because it's lost between process
  37. self.simulation = simulation
  38. self.intentions = None
  39. if self.behaviour_selector_class:
  40. self.behaviour_selector = self.behaviour_selector_class()
  41. else:
  42. self.behaviour_selector = SubjectBehaviourSelector()
  43. if self.intention_manager_class:
  44. self.intentions = self.intention_manager_class()
  45. else:
  46. self.intentions = IntentionManager()
  47. @property
  48. def id(self) -> int:
  49. try:
  50. return self._id
  51. except AttributeError:
  52. pass
  53. def change_id(self, id_: int) -> None:
  54. self._id = id_
  55. def expose(self) -> None:
  56. subject_behaviours_index = shared.get('subject_behaviours_index').setdefault(self._id, [])
  57. subject_mechanisms_index = shared.get('subject_mechanisms_index').setdefault(self._id, [])
  58. subject_classes = shared.get('subject_classes')
  59. for behaviour_class in self.behaviours_classes:
  60. subject_behaviours_index.append(id(behaviour_class))
  61. for mechanism_class in behaviour_class.use:
  62. subject_mechanisms_index.append(id(mechanism_class))
  63. subject_classes[self._id] = id(type(self))
  64. for collection in self.collections:
  65. self.simulation.collections.setdefault(collection, []).append(self.id)
  66. def __str__(self):
  67. return self.__repr__()
  68. def __repr__(self):
  69. return '{}({})'.format(
  70. type(self).__name__,
  71. self.id,
  72. )
  73. class Subjects(list):
  74. """
  75. TODO: Manage other list methods
  76. """
  77. subject_ids = shared.create('subject_ids', [])
  78. def __init__(self, *args, **kwargs):
  79. self.simulation = kwargs.pop('simulation')
  80. self.removes = []
  81. self.adds = []
  82. self.track_changes = False
  83. self.index = {}
  84. self._auto_expose = True
  85. super().__init__(*args, **kwargs)
  86. if self.auto_expose:
  87. for subject in self:
  88. subject.expose()
  89. @property
  90. def auto_expose(self) -> bool:
  91. return self._auto_expose
  92. @auto_expose.setter
  93. def auto_expose(self, value: bool) -> None:
  94. assert self._auto_expose
  95. self._auto_expose = value
  96. def remove(self, value: Subject):
  97. # Remove from index
  98. del self.index[value.id]
  99. self.subject_ids.remove(value.id)
  100. # Remove from subjects list
  101. super().remove(value)
  102. # Remove from collections
  103. for collection_name in value.collections:
  104. self.simulation.collections[collection_name].remove(value)
  105. # Add to removed listing
  106. if self.track_changes:
  107. self.removes.append(value)
  108. # TODO: Supprimer des choses du shared ! Sinon fuite mémoire dans la bdd
  109. def append(self, p_object):
  110. # Add to index
  111. self.index[p_object.id] = p_object
  112. self.subject_ids.append(p_object.id)
  113. # Add to subjects list
  114. super().append(p_object)
  115. # Add to adds list
  116. if self.track_changes:
  117. self.adds.append(p_object)
  118. if self.auto_expose:
  119. p_object.expose()
  120. def extend(self, iterable):
  121. for item in iterable:
  122. self.append(item)
  123. class Simulation(BaseObject):
  124. accepted_subject_class = Subjects
  125. behaviours_classes = []
  126. subject_behaviours_index = shared.create('subject_behaviours_index', {})
  127. subject_mechanisms_index = shared.create('subject_mechanisms_index', {})
  128. subject_classes = shared.create('subject_classes', {})
  129. collections = shared.create('collections', {})
  130. def __init__(
  131. self,
  132. config: Config,
  133. ):
  134. self.config = config
  135. self._subjects = None # type: Subjects
  136. # Should contain all usable class of Behaviors, Mechanisms, SubjectBehaviourSelectors,
  137. # IntentionManagers, Subject
  138. self._index = {} # type: typing.Dict[int, type]
  139. self._index_locked = False
  140. self.behaviours = {}
  141. self.mechanisms = {}
  142. for mechanism_class in get_mechanisms_classes(self):
  143. self.mechanisms[mechanism_class.__name__] = mechanism_class(
  144. config=self.config,
  145. simulation=self,
  146. )
  147. for behaviour_class in self.behaviours_classes:
  148. self.behaviours[behaviour_class.__name__] = behaviour_class(
  149. config=self.config,
  150. simulation=self,
  151. )
  152. def add_to_index(self, *classes: type) -> None:
  153. assert not self._index_locked
  154. for class_ in classes:
  155. self._index[id(class_)] = class_
  156. @property
  157. def index(self) -> typing.Dict[int, type]:
  158. return self._index
  159. def lock_index(self) -> None:
  160. self._index_locked = True
  161. @property
  162. def subjects(self):
  163. return self._subjects
  164. @subjects.setter
  165. def subjects(self, value: 'Subjects'):
  166. if not isinstance(value, self.accepted_subject_class):
  167. raise Exception('Simulation.subjects must be {0} type'.format(
  168. self.accepted_subject_class,
  169. ))
  170. self._subjects = value
  171. def get_or_create_subject(self, subject_id: int) -> Subject:
  172. try:
  173. return self._subjects.index[subject_id]
  174. except KeyError:
  175. # We should be in process context and subject have to been created
  176. subject_class_id = shared.get('subject_classes')[subject_id]
  177. subject_class = self.index[subject_class_id]
  178. subject = subject_class(self.config, self)
  179. subject.change_id(subject_id)
  180. self.subjects.append(subject)
  181. return subject
  182. class Mechanism(BaseObject):
  183. pass
  184. class SubjectMechanism(Mechanism):
  185. def __init__(
  186. self,
  187. config: Config,
  188. simulation: Simulation,
  189. subject: Subject,
  190. ):
  191. self.config = config
  192. self.simulation = simulation
  193. self.subject = subject
  194. def run(self):
  195. raise NotImplementedError()
  196. class SimulationMechanism(Mechanism):
  197. """If parallelizable behaviour, call """
  198. parallelizable = False
  199. def __init__(
  200. self,
  201. config: Config,
  202. simulation: Simulation,
  203. ):
  204. self.config = config
  205. self.simulation = simulation
  206. def repr_debug(self) -> str:
  207. return self.__class__.__name__
  208. def run(self, process_number: int=None, process_count: int=None):
  209. raise NotImplementedError()
  210. class Event(BaseObject):
  211. def __init__(self, *args, **kwargs):
  212. pass
  213. def repr_debug(self) -> str:
  214. return self.__class__.__name__
  215. class Behaviour(BaseObject):
  216. def run(self, data):
  217. raise NotImplementedError()
  218. class SubjectBehaviour(Behaviour):
  219. frequency = 1
  220. use = [] # type: typing.List[typing.Type[SubjectMechanism]]
  221. def __init__(
  222. self,
  223. config: Config,
  224. simulation: Simulation,
  225. subject: Subject,
  226. ):
  227. self.config = config
  228. self.simulation = simulation
  229. self.subject = subject
  230. def run(self, data):
  231. """
  232. Method called in subprocess.
  233. If return equivalent to False, behaviour produce nothing.
  234. If return equivalent to True, action bahaviour method
  235. will be called with these data
  236. Note: Returned data will be transfered from sub processes.
  237. Prefer scalar types.
  238. """
  239. raise NotImplementedError() # TODO Test it and change to strictly False
  240. def action(self, data) -> [Event]:
  241. """
  242. Method called in main process
  243. Return events will be give to terminals
  244. """
  245. raise NotImplementedError()
  246. class SimulationBehaviour(Behaviour):
  247. frequency = 1
  248. use = []
  249. def __init__(
  250. self,
  251. config: Config,
  252. simulation: Simulation,
  253. ):
  254. self.config = config
  255. self.simulation = simulation
  256. def run(self, data):
  257. """
  258. Method called in subprocess if mechanisms are
  259. parallelizable, in main process if not.
  260. """
  261. raise NotImplementedError()
  262. @classmethod
  263. def merge_data(cls, new_data, start_data=None):
  264. """
  265. Called if behaviour executed in subprocess
  266. """
  267. raise NotImplementedError()
  268. def action(self, data) -> [Event]:
  269. """
  270. Method called in main process
  271. Return events will be give to terminals
  272. """
  273. raise NotImplementedError()
  274. class SubjectBehaviourSelector(BaseObject):
  275. def reduce_behaviours(
  276. self,
  277. behaviours: typing.Dict[typing.Type[SubjectBehaviour], object],
  278. ) -> typing.Dict[typing.Type[SubjectBehaviour], object]:
  279. return behaviours