The Higher Education and Research forge

Home My Page Projects Code Snippets Project Openings EMULSION public releases
Summary Activity Surveys SCM Listes Sympa

SCM Repository

1 """
2 .. module:: emulsion.agent.managers.functions
4 .. moduleauthor:: Sébastien Picault <sebastien.picault@inra.fr>
6 """
9 # EMULSION (Epidemiological Multi-Level Simulation framework)
10 # ===========================================================
11
12 # Contributors and contact:
13 # -------------------------
14
15 #     - Sébastien Picault (sebastien.picault@inra.fr)
16 #     - Yu-Lin Huang
17 #     - Vianney Sicard
18 #     - Sandie Arnoux
19 #     - Gaël Beaunée
20 #     - Pauline Ezanno (pauline.ezanno@inra.fr)
21
22 #     BIOEPAR, INRA, Oniris, Atlanpole La Chantrerie,
23 #     Nantes CS 44307 CEDEX, France
24
25
26 # How to cite:
27 # ------------
28
29 #     S. Picault, Y.-L. Huang, V. Sicard, P. Ezanno (2017). "Enhancing
30 #     Sustainability of Complex Epidemiological Models through a Generic
31 #     Multilevel Agent-based Approach", in: C. Sierra (ed.), 26th
32 #     International Joint Conference on Artificial Intelligence (IJCAI),
33 #     AAAI, p. 374-380. DOI: 10.24963/ijcai.2017/53
34
35
36 # License:
37 # --------
38
39 #    Copyright 2016 INRA and Univ. Lille
40
41 #    Inter Deposit Digital Number: IDDN.FR.001.280043.000.R.P.2018.000.10000
42
43 #    Agence pour la Protection des Programmes,
44 #    54 rue de Paradis, 75010 Paris, France
45
46 #    Licensed under the Apache License, Version 2.0 (the "License");
47 #    you may not use this file except in compliance with the License.
48 #    You may obtain a copy of the License at
49
50 #        http://www.apache.org/licenses/LICENSE-2.0
51
52 #    Unless required by applicable law or agreed to in writing, software
53 #    distributed under the License is distributed on an "AS IS" BASIS,
54 #    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55 #    See the License for the specific language governing permissions and
56 #    limitations under the License.
58 from   collections               import OrderedDict
60 from   sortedcontainers          import SortedDict
62 from   emulsion.agent.views      import StructuredView
63 from   emulsion.tools.misc       import count_population, rewrite_keys
65 from   emulsion.agent.managers.functions import group_and_split_populations
67 #   _____                       __  __
68 #  / ____|                     |  \/  |
69 # | |  __ _ __ ___  _   _ _ __ | \  / | __ _ _ __   __ _  __ _  ___ _ __
70 # | | |_ | '__/ _ \| | | | '_ \| |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|
71 # | |__| | | | (_) | |_| | |_) | |  | | (_| | | | | (_| | (_| |  __/ |
72 #  \_____|_|  \___/ \__,_| .__/|_|  |_|\__,_|_| |_|\__,_|\__, |\___|_|
73 #                        | |                              __/ |
74 #                        |_|                             |___/
76 class GroupManager(StructuredView):
77     """An GroupManager is able to make its content
78     evolve according to a specific state machine, the state of each
79     subcompartment being stored in a specific state variable or
80     attribute.
82     """
83     def __init__(self, state_machine=None, **others):
84         """Create an GroupManager based on the
85         specified state machine. The state of each subcompartment can
86         be retrieved in the specified statevar name ('true' statevar
87         or attribute)
89         """
90         ### WARNING: strange bug found sometimes when content={} not
91         ### explicitly specified, another content (from another
92         ### instance ???) may be used instead !!!!
93         super().__init__(**others)
94         self._content = SortedDict()
95         self.state_machine = state_machine
96         self.process_name = None
97         self.init_counts()
99     def init_counts(self, index=0):
100         """Initialize the counts."""
101         ## DEBUG
102         # print('STEP', self.statevars.step, '\nInit counts in GM', self)
103         self.counts = {}
104         if self.state_machine is not None:
105             self.counts = {state.name: [] if self.keep_history else 0
106                            for state in self.state_machine.states}
107             self.counts['step'] = [] if self.keep_history else 0
108         else:
109             super().init_counts()
110         ## DEBUG
111         # print(self.counts)
113     def update_counts(self, index=0):
114         """Update the number of atoms for each state of the state
115         machine (TODO: for each value of the key[index] enum).
117         """
118         if self.state_machine is not None:
119             total = {state.name: 0 for state in self.state_machine.states}
120             ## DEBUG
121             # print('\t', self.statevars.step, sep='')
122             for (key, unit) in self._content.items():
123                 if key[index] is not None:
124                     total[key[index].name] += unit.get_information('population')
125                     # total[key[index].name] += unit.population
126                     ## DEBUG
127                     # print(key, unit.population, sep=' + ')
128             # print()
129             if self.keep_history:
130                 self.counts['step'].append(self.statevars.step)
131                 for state in self.state_machine.states:
132                     self.counts[state.name].append(total[state.name])
133             else:
134                 self.counts['step'] = self.statevars.step
135                 self.counts.update(total)
136         else:
137             super().update_counts()
138         ## DEBUG
139         # print('STEP', self.statevars.step, '\nUPDATE', self.counts)
141     def apply_changes(self, transitions, productions):
142         """Apply modifications to the compartments contained in the current
143         StructuredView, according to `transitions` and
144         `productions`. Dictionary `transitions` is keyed by a tuple of
145         variables and associated with a list of dictionaries, either
146         {'population': qty, 'actions': list} or {'agents': list,
147         'actions': list}. List `productions` contains tuples (target,
148         {'population': qty}, None) or (target, {'agents': list},
149         prototype).
151         """
152         for source, evolutions in transitions.items():
153             for target, population_or_agents in evolutions:
154                 target_comp = self.get_or_build(target, source=self[source])
155                 self._content[source].move_to(
156                     target_comp,
157                     state_machine=self.state_machine,
158                     **population_or_agents)
159         self.new_population = productions
161     def evolve(self, machine=None):
162         super().evolve(machine=machine)
163         ## DEBUG
164         # print(self.statevars.step, self.counts)
165         self.evolve_states()
166         ## DEBUG
167         # print(self.statevars.step, self.counts)
168         for key, comp in self._content.items():
169             if comp.autoremove:
170                 agents_or_population = comp.get_content()
171                 if agents_or_population[0] == 'population':
172                     agents_or_population = (agents_or_population[0],
173                                             {self.process_name: {
174                                                 key: agents_or_population[1]
175                                             }})
176                 self._host.remove(agents_or_population)
177         self.update_counts()
178         ## DEBUG
179         # print(self.statevars.step, self.counts)
181     def evolve_states(self, machine=None):
182         """Ask each compartment to make its content evolve according
183         to its current state and the specified state_machine.
185         """
186         self.new_population = None
187         transitions = self._evolve_transitions(machine=machine)
188         productions = self._evolve_productions(machine=machine)
189         self.apply_changes(transitions, productions)
191     def _evolve_transitions(self, machine=None):
192         # init empty dictionary for all changes to perform
193         future = OrderedDict()
194         # iterate over all compartments
195         for name, compart in self._content.items():
196             future[name] = []
197             # compute the current population of each source compartment
198             current_pop = compart.get_information('population')
199             # no action if current pop <= 0
200             if current_pop <= 0:
201                 continue
202             # compute all possible transitions from the current state
203             current_state = compart.get_information(
204                 self.state_machine.machine_name)
205             # execute actions on stay for current state
206             compart.do_state_actions('on_stay', self.state_machine,
207                                      current_state.name,
208                                      **dict([compart.get_content()]))
209             # get the possible transitions from the current state
210             # i.e. a list of tuples (state, flux, value, cond_result,
211             # actions) where:
212             # - state is a possible state reachable from the current state
213             # - flux is either 'rate' or 'proba' or 'amount' or 'amount-all-but'
214             # - value is the corresponding rate or probability or amount
215             # - cond_result is a tuple (either ('population', qty) or
216             # ('agents', list)) describing who fulfills the condition to cross
217             # the transition
218             # - actions is the list of actions on cross
219             transitions = compart.next_states_from(current_state.name,
220                                                    self.state_machine)
221             # print('TRANSITIONS = ', name, '->', transitions)
222             # nothing to do if no transitions
223             if not transitions:
224                 continue
226             ### REWRITE TRANSITIONS TO HAVE DISJOINT SUB-POPULATIONS
227             transitions_by_pop = group_and_split_populations(transitions)
228             # print(transitions_by_pop)
229             for ref_pop, properties in transitions_by_pop:
230                 # retrieve the list of states, the list of flux, the
231                 # list of values, the list of populations affected by
232                 # each possible transition
233                 states, flux, values, actions = zip(*properties)
234                 # print(name, '->\n\t', states, values, [ag._agid
235                 #                                        for u in populations
236                 #                                        for ag in u['agents']])
237                 # add the current state to the possible destination states...
238                 states = states + (current_state.name,)
239                 # ... with no action
240                 actions = actions + ([], )
241                 #
242                 values, method = self._compute_values_for_unique_population(
243                     values, flux, ref_pop, compart.stochastic)
244                 change_list = compart.next_states(states,
245                                                   values,
246                                                   [ref_pop],
247                                                   actions, method=method)
248                 future[name] += rewrite_keys(name, name.index(current_state),
249                                               change_list)
250         # print('FUTURE:', future)
251         return future
254     def _evolve_productions(self, machine=None):
255         # init empty list for all changes to perform
256         future = []
257         # iterate over all compartments
258         for name, compart in self._content.items():
259             # compute the current population of each source compartment
260             current_pop = max(compart.get_information('population'), 0)
261             # no action if "fake" compartment
262             if set(name) == {None}:
263                 continue
264             # # no action if current pop <= 0
265             # if current_pop <= 0:
266             #     continue
267             # compute all possible transitions from the current state
268             current_state = compart.get_information(self.state_machine.machine_name)
269             # get the possible transitions from the current state
270             # i.e. a list of tuples (state, flux, value, cond_result,
271             # prototype) where:
272             # - state is a possible state producible from the current state
273             # - flux is either 'rate' or 'proba' or 'amount' or 'amount-all-but'
274             # - value is the corresponding rate or probability or amount
275             # - cond_result is a tuple (either ('population', qty) or
276             # ('agents', list)) describing who fulfills the condition to cross
277             # the transition
278             # - prototype is the prototype for creating new agents
279             productions = compart.production_from(current_state.name,
280                                                   self.state_machine)
281             # print('PRODUCTIONS = ', productions)
282             # nothing to do if no transitions
283             if not productions:
284                 continue
285             ### HERE WE ASSUME THAT AN AGENT CAN PRODUCE SEVERAL OTHER
286             ### AGENTS SIMULTANEOUSLY (OTHERWISE USE CONDITIONS)
287             ### REWRITE TRANSITIONS TO HAVE DISJOINT SUB-POPULATIONS
288             for target_state, flux, values, ref_pop, proto in productions:
289                 pop_size = count_population(ref_pop)
290                 amount = self._compute_production(values, flux, pop_size, compart.stochastic)
291                 if amount > 0:
292                     future.append((target_state, amount, proto))
293         # print('FUTURE:', future)
294         return future