simulation.py 10KB

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