simulation.py 10KB

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