seed2lp 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. seed2lp/__init__.py +12 -0
  2. seed2lp/__main__.py +837 -0
  3. seed2lp/_version.py +2 -0
  4. seed2lp/argument.py +717 -0
  5. seed2lp/asp/atom_for_transfers.lp +7 -0
  6. seed2lp/asp/community_heuristic.lp +3 -0
  7. seed2lp/asp/community_search.lp +14 -0
  8. seed2lp/asp/constraints_targets.lp +15 -0
  9. seed2lp/asp/definition_atoms.lp +87 -0
  10. seed2lp/asp/enum-cc.lp +50 -0
  11. seed2lp/asp/flux.lp +70 -0
  12. seed2lp/asp/limit_transfers.lp +9 -0
  13. seed2lp/asp/maximize_flux.lp +2 -0
  14. seed2lp/asp/maximize_produced_target.lp +7 -0
  15. seed2lp/asp/minimize.lp +8 -0
  16. seed2lp/asp/seed-solving.lp +116 -0
  17. seed2lp/asp/seed_external.lp +1 -0
  18. seed2lp/asp/show_seeds.lp +2 -0
  19. seed2lp/asp/show_tranfers.lp +1 -0
  20. seed2lp/asp/test.lp +61 -0
  21. seed2lp/clingo_lpx.py +236 -0
  22. seed2lp/color.py +34 -0
  23. seed2lp/config.yaml +56 -0
  24. seed2lp/description.py +424 -0
  25. seed2lp/file.py +151 -0
  26. seed2lp/flux.py +365 -0
  27. seed2lp/linear.py +431 -0
  28. seed2lp/log_conf.yaml +25 -0
  29. seed2lp/logger.py +112 -0
  30. seed2lp/metabolite.py +46 -0
  31. seed2lp/network.py +1921 -0
  32. seed2lp/reaction.py +207 -0
  33. seed2lp/reasoning.py +459 -0
  34. seed2lp/reasoningcom.py +753 -0
  35. seed2lp/reasoninghybrid.py +791 -0
  36. seed2lp/resmod.py +74 -0
  37. seed2lp/sbml.py +307 -0
  38. seed2lp/scope.py +124 -0
  39. seed2lp/solver.py +333 -0
  40. seed2lp/temp_flux_com.py +74 -0
  41. seed2lp/utils.py +237 -0
  42. seed2lp-2.0.0.dist-info/METADATA +404 -0
  43. seed2lp-2.0.0.dist-info/RECORD +53 -0
  44. seed2lp-2.0.0.dist-info/WHEEL +5 -0
  45. seed2lp-2.0.0.dist-info/entry_points.txt +2 -0
  46. seed2lp-2.0.0.dist-info/licenses/LICENCE.txt +145 -0
  47. seed2lp-2.0.0.dist-info/top_level.txt +2 -0
  48. tests/__init__.py +0 -0
  49. tests/fba.py +147 -0
  50. tests/full_network.py +166 -0
  51. tests/normalization.py +188 -0
  52. tests/target.py +286 -0
  53. tests/utils.py +181 -0
seed2lp/reaction.py ADDED
@@ -0,0 +1,207 @@
1
+ # Object Reaction constitued of
2
+ # - name: name/id of reaction
3
+ # - reversible (bool): Set if the reaction is reversible
4
+ # - lbound (float): Lower bound of a reaction
5
+ # - ubound (float): Upper bound of a reaction
6
+ # - Reactants (list): List of reactants (object Metabolite)
7
+ # - Products (list): List of list of products (object Metabolite)
8
+
9
+ from seed2lp.metabolite import Metabolite
10
+ from . import logger
11
+
12
+ class Reaction:
13
+ def __init__(self, name:str, reversible:bool=False, lbound:float=None, ubound:float=None,
14
+ species:str=""):
15
+ """Initialize Object Reaction
16
+
17
+ Args:
18
+ name (str): name/id of reaction
19
+ reversible (bool, optional): Set if the reaction is reversible. Defaults to False.
20
+ lbound (float, optional): Lower bound of a reaction. Defaults to None.
21
+ ubound (float, optional): Upper bound of a reaction. Defaults to None.
22
+ """
23
+ self.name = name
24
+ self.reversible = reversible
25
+ self.lbound = lbound
26
+ self.ubound = ubound
27
+ self.reactants = list()
28
+ self.products = list()
29
+ self.is_exchange = False
30
+ self.is_transport = False
31
+ self.is_meta_modified = False
32
+ self.is_reversible_modified = False
33
+ self.species = species
34
+ self.has_rm_prefix = False
35
+
36
+
37
+ ######################## SETTER ########################
38
+ def _set_reactants(self, reactants_list:list):
39
+ self.reactants = reactants_list
40
+
41
+ def _set_products(self, products_list:list):
42
+ self.products = products_list
43
+ ########################################################
44
+
45
+ ######################## METHODS ########################
46
+ def add_metabolites_from_list(self, metabolites_list:list, metabolite_type:str,
47
+ meta_exchange_list:list, meta_transport_list:list,
48
+ used_meta:dict):
49
+ """Add all Metabolite from a list as a Reactant or Product list
50
+
51
+ Args:
52
+ metabolites_list (list): List of metabolite
53
+ metabolite_type (str): Type of metabolite to construct object list (Reactant or Product)
54
+ meta_exchange_list (list): List of exchanged metabolite
55
+ meta_transport_list (list): List of transport metabolite
56
+ used_meta (dict): Dictionnary of used metabolite in the network contining list of reaction they appeared
57
+ """
58
+ meta_list=[]
59
+ for meta in metabolites_list:
60
+ metabolite = Metabolite(meta[0], meta[2], round(float(meta[1]),10), species=self.species)
61
+ # A reaction is an exchange reaction
62
+ # Exchange reaction involving multiple metabolites are not considered as exchange
63
+ # but will me removed into write_facts() function
64
+ if self.is_exchange:
65
+ # The metabolite is tagged as exchange
66
+ if metabolite.id_meta not in meta_exchange_list:
67
+ meta_exchange_list.append(metabolite.id_meta)
68
+ if metabolite.id_meta in meta_transport_list:
69
+ meta_transport_list.remove(metabolite.id_meta)
70
+ # we do not want to change exchange tag into transport tag
71
+ # The reaction has to be taggued transport
72
+ elif self.is_transport \
73
+ and (metabolite.id_meta not in meta_exchange_list):
74
+ # The metabolite is tagged as exchange
75
+ if metabolite.id_meta not in meta_transport_list:
76
+ meta_transport_list.append(metabolite.id_meta)
77
+ metabolite.type = "transport"
78
+ # A reaction is not an exchange reaction nor transport reaction
79
+ # Exchange reactions involving multiple metabolites are treated like intern metabolite
80
+ else:
81
+ # Check if the metabolite already existe in list of network
82
+ if metabolite.id_meta in meta_exchange_list:
83
+ metabolite.type = "exchange"
84
+ elif metabolite.id_meta in meta_transport_list:
85
+ metabolite.type = "transport"
86
+ else:
87
+ metabolite.type = "other"
88
+ meta_list.append(metabolite)
89
+ if metabolite.id_meta in used_meta:
90
+ used_meta[metabolite.id_meta].append(self.name)
91
+ else:
92
+ used_meta[metabolite.id_meta] = [self.name]
93
+
94
+ meta_list.sort(key=lambda x: x.id_meta)
95
+ match metabolite_type:
96
+ case "reactant":
97
+ self._set_reactants(meta_list)
98
+ case "product":
99
+ self._set_products(meta_list)
100
+ return meta_exchange_list, meta_transport_list, used_meta
101
+
102
+
103
+ def convert_to_facts(self, keep_import_reactions, use_topological_injections):
104
+ """Correcting the Network an convert into facts
105
+
106
+ Args:
107
+ keep_import_reactions (bool): Import reactions are not removed
108
+ use_topological_injections (bool): Metabolite of import reaction are seeds
109
+ """
110
+ upper = self.ubound
111
+ rev_upper = -self.lbound
112
+ lower = round(float(0),10)
113
+ facts = ""
114
+ if self.reversible or (not self.reversible and self.has_rm_prefix):
115
+ # When the reaction is reversible, the reaction is splitted
116
+ # Create 2 different reactions going in one way for reversible reaction
117
+ # Cases : [-1000, 1000] | [-8, 1000] | [-1000, 8]
118
+ # divided in : 2*[0,1000] | [0,1000] and rev_[0,8] | [0,8] and rev_[0,1000]
119
+ #lower= round(float(0),10)
120
+ facts += self.write_facts(lower,
121
+ rev_upper,
122
+ self.products,
123
+ self.reactants,
124
+ keep_import_reactions,
125
+ use_topological_injections,
126
+ self.reversible,
127
+ True)
128
+
129
+
130
+ # Upper bound does not change on forward reaction
131
+ facts += self.write_facts(lower,
132
+ upper,
133
+ self.reactants,
134
+ self.products,
135
+ keep_import_reactions,
136
+ use_topological_injections,
137
+ self.reversible,
138
+ False)
139
+ return facts
140
+
141
+
142
+
143
+ def write_facts(self, lbound:float, ubound:float, reactants:list, products:list,
144
+ keep_import_reactions:bool, use_topological_injections:bool,
145
+ reversible:bool, is_reversed:bool):
146
+ """Convert the description of a reaction into ASP facts after correcting the Network
147
+
148
+ Args:
149
+ lbound (float): Lower bound of a reaction
150
+ ubound (float): Upper bound of a reaction
151
+ reactants (list): List of Reactants (Metabolite)
152
+ products (list): List of Products (Metabolite)
153
+ keep_import_reactions (bool): Import reactions are not removed
154
+ use_topological_injections (bool): Metabolite of import reaction are seeds
155
+ reversible (bool): Define if the reaction is reversible.
156
+ is_reversed (bool): Define if the reaction is the reversed writtent as "Rev_R_*"
157
+
158
+ Returns:
159
+ str: Reaction converted into ASP Facts
160
+ """
161
+
162
+ #Add reverse reaction
163
+ # If mode remove_rxn, the import reactions (none -> metabolite)
164
+ # are removed
165
+ facts=""
166
+
167
+ # Delete both exchange reaction and multiple metabolite exchange reaction
168
+ # A reaction is multiple exchange
169
+ # when None -> A + B || A + B -> None || None <-> A + B || A + B <-> None
170
+ is_import_reaction = not reactants
171
+
172
+ if not is_reversed:
173
+ name = self.name
174
+ if reversible:
175
+ facts += f'reversible("{name}","rev_{self.name}").\n'
176
+ else:
177
+ name = f'rev_{self.name}'
178
+
179
+ # only one metabolite involved exchange reaction are tagued as exchange
180
+ # metabolite of exchange reaction involving multipele metabolite are managed like internal metabolites
181
+ if self.is_exchange:
182
+ facts += f'exchange("{name}").\n'
183
+
184
+ prefix=""
185
+ # hes rm prefix is True only when an import reaction has been deleted from network but we need to keep
186
+ # a trace of bondaries into asp fact, the rm_ prefix is needed for the reverse of the remaning reactions
187
+ if not keep_import_reactions and (is_import_reaction or (self.has_rm_prefix and is_reversed)):
188
+ prefix = "rm_"
189
+ logger.log.info(f"Reaction {self.name} artificially removed into lp facts with a prefix 'rm_'")
190
+
191
+ facts += f'{prefix}reaction("{name}").\n'
192
+ facts += f'{prefix}bounds("{name}","{"{:.10f}".format(lbound)}","{"{:.10f}".format(ubound)}").\n'
193
+
194
+
195
+ for metabolite in reactants:
196
+ facts += metabolite.convert_to_facts(f"{prefix}reactant", name)
197
+
198
+ for metabolite in products:
199
+ facts += metabolite.convert_to_facts(f"{prefix}product", name)
200
+ # Because of reversibility it is called 2 time
201
+ # Only necessary to do this for products
202
+ if use_topological_injections and is_import_reaction: # this reaction is a generator of seed
203
+ facts += metabolite.convert_to_facts("seed")
204
+
205
+ return facts
206
+
207
+ ########################################################
seed2lp/reasoning.py ADDED
@@ -0,0 +1,459 @@
1
+ # Object Reasoning, herit from Solver, added properties:
2
+ # - grounded (str): All rules grounded before solving to save time
3
+
4
+ import clyngor
5
+ from time import time
6
+ from .network import Network
7
+ from .reasoninghybrid import HybridReasoning
8
+ from . import color, logger
9
+
10
+
11
+ ###################################################################
12
+ ############ Class Reasoning : herit HybridReasoning ##############
13
+ ###################################################################
14
+ class Reasoning(HybridReasoning):
15
+ def __init__(self, run_mode:str, run_solve:str, network:Network,
16
+ time_limit_minute:float=None, number_solution:int=None,
17
+ clingo_configuration:str=None, clingo_strategy:str=None,
18
+ intersection:bool=False, union:bool=False,
19
+ minimize:bool=False, subset_minimal:bool=False,
20
+ temp_dir:str=None, short_option:str=None,
21
+ verbose:bool=False, community_mode:str=None,
22
+ all_transfers:bool=False):
23
+ """Initialize Object Reasoning, herit from HybridReasoning
24
+
25
+ Args:
26
+ run_mode (str): Running command used (full or target)
27
+ network (Network): Network constructed
28
+ time_limit_minute (float, optional): Time limit given by user in minutes. Defaults to None.
29
+ number_solution (int, optional): Limit number of solutions to find. Defaults to None.
30
+ clingo_configuration (str, optional): Configuration for clingo resolution. Defaults to None.
31
+ clingo_strategy (str, optional): Strategy for clingo resolution. Defaults to None.
32
+ intersection (bool, optional): Find the intersection of all solutions without limitation (give one solution). Defaults to False.
33
+ union (bool, optional): Find the union of all solutions without limitation (give one solution). Defaults to False.
34
+ minimize (bool, optional): Search the minimal carinality of solutions. Defaults to False.
35
+ subset_minimal (bool, optional): Search the subset minimal solutions. Defaults to False.
36
+ temp_dir (str, optional): Temporary directory for saving instance file and clingo outputs. Defaults to None.
37
+ short_option (str, optional): Short way to write option on filename. Defaults to None.
38
+ verbose (bool, optional): Set debug mode. Defaults to False.
39
+ """
40
+ super().__init__(run_mode, network, time_limit_minute, number_solution, clingo_configuration,
41
+ clingo_strategy, intersection, union, minimize, subset_minimal,
42
+ temp_dir, short_option, run_solve, verbose, community_mode, all_transfers)
43
+
44
+ self.is_linear = False
45
+ title_mess = "\n############################################\n" \
46
+ "############################################\n" \
47
+ f" {color.bold}REASONING{color.cyan_light}\n"\
48
+ "############################################\n" \
49
+ "############################################\n"
50
+ logger.print_log(title_mess, "info", color.cyan_light)
51
+ self._set_clingo_constant()
52
+ self._set_temp_result_file()
53
+
54
+
55
+
56
+ ######################## SETTER ########################
57
+ def _set_clingo_constant(self):
58
+ """Prepare ASP constant command for resolution
59
+ """
60
+ self.init_const()
61
+ logger.print_log(f"Time limit: {self.time_limit_minute} minutes", "info")
62
+ logger.print_log( f"Solution number limit: {self.number_solution}", "info")
63
+ ########################################################
64
+
65
+
66
+ ######################## METHODS ########################
67
+ def reinit_optimum(self):
68
+ """Reinit optimum data to launch all modes
69
+ """
70
+ self.optimum = None
71
+ self.optimum_found = False
72
+ self.opt_prod_tgt = None
73
+ self.opt_size = None
74
+
75
+ def search_seed(self):
76
+ """Launch seed searching
77
+ """
78
+ self.asp_files.append(self.asp.ASP_SRC_SHOW_SEEDS)
79
+
80
+ timer=dict()
81
+ # Subset minimal mode: By default the sub_seeds search from possible seed given is deactivated
82
+ if self.subset_minimal:
83
+ self.get_message('subsetmin')
84
+ if self.ground:
85
+ timer = self.reasoning_ground(self.asp_files)
86
+ if self.run_solve == "reasoning" or self.run_solve == "all":
87
+ self.get_message('classic')
88
+ self.search_subsetmin(timer, 'classic')
89
+
90
+ if self.run_solve == "filter" or self.run_solve == "all":
91
+ self.get_message('filter')
92
+ self.search_subsetmin(timer, 'filter')
93
+
94
+ if self.run_solve == "guess_check" or self.run_solve == "all":
95
+ self.get_message('guess_check')
96
+ self.search_subsetmin(timer, 'guess_check')
97
+
98
+ if self.run_solve == "guess_check_div" or self.run_solve == "all":
99
+ self.get_message('guess_check_div')
100
+ self.search_subsetmin(timer, 'guess_check_div')
101
+
102
+
103
+ if self.minimize:
104
+ self.get_message('minimize')
105
+ if self.network.is_subseed:
106
+ self.asp_files.append(self.asp.ASP_SRC_MAXIMIZE_PRODUCED_TARGET)
107
+ logger.print_log('POSSIBLE SEED: Given\n A subset of possible seed is search \n maximising the number of produced target', 'info')
108
+ self.clingo_constant.append('-c')
109
+ self.clingo_constant.append('subseed=1')
110
+ self.asp_files.append(self.asp.ASP_SRC_MINIMIZE)
111
+
112
+ if self.ground:
113
+ timer = self.reasoning_ground(self.asp_files)
114
+
115
+ if self.run_solve == "reasoning" or self.run_solve == "all":
116
+ self.get_message('classic')
117
+ self.search_minimize(timer, 'classic')
118
+ self.reinit_optimum()
119
+
120
+ if self.run_solve == "filter" or self.run_solve == "all":
121
+ self.get_message('filter')
122
+ self.search_minimize(timer, 'filter')
123
+ self.reinit_optimum()
124
+
125
+ if self.run_solve == "guess_check" or self.run_solve == "all":
126
+ self.get_message('guess_check')
127
+ self.search_minimize(timer, 'guess_check')
128
+ self.reinit_optimum()
129
+
130
+
131
+ if self.run_solve == "guess_check_div" or self.run_solve == "all":
132
+ self.get_message('guess_check_div')
133
+ self.search_minimize(timer, 'guess_check_div')
134
+ self.reinit_optimum()
135
+ self.get_message('end')
136
+
137
+
138
+
139
+ def reasoning_ground(self, asp_files:list):
140
+ """Ground the ASP files to create all facts from rules
141
+
142
+ Args:
143
+ asp_files (list): List of ASP files, included th network asp file saved in temp directory
144
+ """
145
+ timer = dict()
146
+ logger.print_log('self.groundING...', 'info')
147
+ time_ground = time()
148
+ const_option = ""
149
+ const_option = ' '.join(self.clingo_constant)
150
+ self.grounded = clyngor.grounded_program(asp_files, options=const_option)
151
+ time_ground = time() - time_ground
152
+ timer["Grounding time"] = round(time_ground, 3)
153
+ return timer
154
+
155
+
156
+
157
+ def write_one_model_solution(self, one_model:dict):
158
+ """Construct the outpu for minimize one model solution (finding optimum step)
159
+
160
+ Args:
161
+ one_model (dict): Solution of finding opimum step
162
+
163
+ Returns:
164
+ solution_list (dict): Constructed output
165
+ """
166
+ solution_list = dict()
167
+ seeds = list()
168
+
169
+ if self.optimum is None:
170
+ logger.print_log('\tNo seed found', 'info')
171
+ else:
172
+ self.get_separate_optimum()
173
+ logger.print_log(f"Optimum found.", "info")
174
+ if self.network.is_subseed:
175
+ logger.print_log((f"Number of producible targets: {- self.opt_prod_tgt}"), 'info')
176
+ logger.print_log(f"Minimal size of seed set is {self.opt_size}\n", 'info')
177
+ if self.opt_size > 0:
178
+ seeds = [args[0] for args in one_model.get('seed', ())]
179
+ seeds=list(sorted(seeds))
180
+ else:
181
+ seeds = []
182
+ if self.network.keep_import_reactions:
183
+ logger.print_log("Try with the option remove import reactions.", 'info')
184
+ #logger.print_log(f"\nOne solution:\n{', '.join(map(str, seeds))}\n", 'info')
185
+ solution_list['model_one_solution'] = ["size", self.opt_size] + \
186
+ ["Set of seeds", seeds]
187
+
188
+ return solution_list, seeds
189
+
190
+
191
+ def search_minimize(self, timer:dict, step:str="classic"):
192
+ """Launch seed searching with minimze options
193
+
194
+ Args:
195
+ timer (dict): Timer dictionnary containing grouding time
196
+ step (str, optional): step solving mode (classic, filter, guess_check, guess_check_div). Defaults to "classic".
197
+ """
198
+ logger.print_log("Finding optimum...", "info")
199
+ self.solve("minimize-one-model", timer, self.asp_files, step, True)
200
+
201
+ if not self.optimum_found:
202
+ return
203
+
204
+ if self.optimum == 0 or self.optimum == (0,0): # without or with possible seeds file
205
+ opti_message = "Optimum is 0."
206
+
207
+ ok_opti = self.optimum_found and (self.opt_size > 0)
208
+ if self.intersection:
209
+ if ok_opti:
210
+ self.get_message('intersection')
211
+ self.solve("minimize-intersection", timer, self.asp_files, step)
212
+ else:
213
+ self.get_message('intersection')
214
+ logger.print_log(f"\nNot computed: {opti_message}", "error")
215
+
216
+ if self.union:
217
+ if ok_opti:
218
+ self.get_message('union')
219
+ self.solve("minimize-union", timer, self.asp_files, step)
220
+ else:
221
+ self.get_message('union')
222
+ logger.print_log(f"\nNot computed: {opti_message}", "error")
223
+
224
+ if self.enumeration:
225
+ if ok_opti:
226
+ self.get_message('enumeration')
227
+ self.solve("minimize-enumeration", timer, self.asp_files, step)
228
+ else:
229
+ self.get_message('enumeration')
230
+ logger.print_log(f"\nNot computed: {opti_message}", "error")
231
+
232
+
233
+ def search_subsetmin(self, timer:dict, step:str="classic"):
234
+ """Launch seed searching with subset minimal options
235
+
236
+ Args:
237
+ timer (dict): Timer dictionnary containing grouding time
238
+ step (str, optional): step solving mode (classic, filter, guess_check, guess_check_div). Defaults to "classic".
239
+ """
240
+ if self.enumeration:
241
+ self.get_message('enumeration')
242
+ self.solve("submin-enumeration", timer, self.asp_files, step)
243
+ else:
244
+ self.number_solution = 1
245
+ self.get_message('One solution')
246
+ self.solve("submin-enumeration", timer, self.asp_files, step)
247
+
248
+ if self.intersection:
249
+ self.get_message('intersection')
250
+ logger.print_log("SOLVING...\n", "info")
251
+ self.solve("submin-intersection", timer, self.asp_files, step)
252
+
253
+
254
+ def get_suffix(self, step:str):
255
+ """Get the corresponding uffix of step solving mode (classic, filter, guess_check, guess_check_div)
256
+
257
+ Args:
258
+ step (str): step solving mode (classic, filter, guess_check, guess_check_div)
259
+
260
+ Returns:
261
+ str: Corresponding suffix to add into solution
262
+ """
263
+ suffix=""
264
+ if step == "filter":
265
+ suffix = " FILTER"
266
+ elif step == "guess_check":
267
+ suffix = " GUESS-CHECK"
268
+ elif step == "guess_check_div":
269
+ suffix = " GUESS-CHECK-DIVERSITY"
270
+ return suffix
271
+
272
+
273
+ def solve(self, search_mode:str, timer:dict, asp_files:list=None, step:str="classic", is_one_model:bool=False):
274
+ """Solve the seed searching using the launch mode
275
+
276
+
277
+ Args:
278
+ search_mode (str): Describe the launch mode.
279
+ timer (dict): Timer dictionnary containing grouding time.
280
+ asp_files (list, optional): List of asp files needed for clingo solving. Defaults to None.
281
+ step (str, optional): step solving mode (classic, filter, guess_check, guess_check_div). Defaults to "classic".
282
+ is_one_model (bool, optional): Define if it is the minimizing one model we are searching. Defaults to False.
283
+ """
284
+
285
+ logger.print_log("SOLVING...\n", "info")
286
+ results = dict()
287
+ one_model = None
288
+ number_rejected = None
289
+ solution_list = dict()
290
+ full_option, mode_message, model_type, output_type = self.get_solutions_infos(search_mode)
291
+
292
+ str_option,full_option = self.construct_string_option(full_option)
293
+
294
+ suffix=self.get_suffix(step)
295
+
296
+ match search_mode, step:
297
+ # CLASSIC MODE (NO FILTER, NO GUESS-CHECK)
298
+ case "minimize-one-model", "classic":
299
+ time_solve = time()
300
+ if self.ground:
301
+ models = clyngor.solve_from_grounded(self.grounded, options=str_option,
302
+ time_limit=self.time_limit).discard_quotes.by_predicate
303
+ else:
304
+ models = clyngor.solve(files=asp_files, options=str_option,
305
+ time_limit=self.time_limit).discard_quotes.by_predicate
306
+ time_solve = time() - time_solve
307
+ self.get_message("command")
308
+ logger.print_log(f'{models.command}', 'debug')
309
+ for model, opt, optimum_found in models.by_arity.with_optimality:
310
+ if optimum_found:
311
+ self.optimum_found = True
312
+ one_model = model
313
+ if one_model.get('seed'):
314
+ self.optimum = opt
315
+ else:
316
+ if self.network.possible_seeds:
317
+ self.optimum = opt
318
+ else:
319
+ self.optimum = 0
320
+ if not self.optimum_found:
321
+ logger.print_log('Optimum not found', "error")
322
+ else:
323
+ solution_list, seeds = self.write_one_model_solution(one_model)
324
+ self.network.add_result_seeds('REASONING', search_mode, model_type, len(seeds), seeds)
325
+
326
+ case "minimize-enumeration" | "submin-enumeration", "classic":
327
+ time_solve = time()
328
+ solution_list = self.solve_enumeration(str_option, solution_list,
329
+ search_mode, asp_files)
330
+ time_solve = time() - time_solve
331
+
332
+
333
+ case _, "classic":
334
+ time_solve = time()
335
+ if self.ground:
336
+ models = clyngor.solve_from_grounded(self.grounded, options=str_option,
337
+ time_limit=self.time_limit).discard_quotes.by_predicate
338
+ else:
339
+ models = clyngor.solve(files=asp_files, options=str_option,
340
+ time_limit=self.time_limit).discard_quotes.by_predicate
341
+ time_solve = time() - time_solve
342
+ self.get_message("command")
343
+ logger.print_log(f'{models.command}', 'debug')
344
+ has_solution=False
345
+ for model in models:
346
+ has_solution=True
347
+ _models = [model]
348
+ if has_solution:
349
+ models = _models
350
+ seeds = [args[0] for args in models[0].get('seed', ())]
351
+ seeds=list(sorted(seeds))
352
+ size = len(seeds)
353
+
354
+ message = color.cyan_light + f"Answer: {mode_message}{color.reset} ({size} seeds) \n{', '.join(map(str, seeds))}\n"
355
+ print(message)
356
+
357
+ solution_list, _ = self.complete_solutions(solution_list, 'model_'+ model_type, len(seeds), seeds)
358
+ self.network.add_result_seeds('REASONING', search_mode, model_type, len(seeds), seeds)
359
+ else:
360
+ logger.print_log('Unsatisfiable problem', "error")
361
+
362
+ # FILTER OR GUESS-CHECK mode
363
+ #TODO redo intersection and union mode
364
+ case _, "filter"| "guess_check" | "guess_check_div":
365
+ time_solve, time_ground, solution_list, number_rejected = self.solve_hybrid(step, full_option, asp_files, search_mode, is_one_model)
366
+
367
+ #TODO: Intersection and union not needed with filter and guess check but
368
+ # do with python the union and intersection of resulted soluion of filer or guess check
369
+
370
+ if step == "filter" or "guess_check" in step:
371
+ if time_ground != -1:
372
+ timer["Grounding time"] = round(time_ground, 3)
373
+ else:
374
+ timer["Grounding time"] = "Time out"
375
+ results = self.get_constructed_result(time_solve, timer, results, solution_list, number_rejected)
376
+ self.output[output_type+suffix] = results
377
+
378
+
379
+
380
+ def solve_enumeration(self, construct_option:str, solution_list:dict,
381
+ search_mode:str, asp_files:list=None):
382
+ """Solve enumeration for Reasoning Classic mode using Clyngor
383
+
384
+ Args:
385
+ construct_option (str): Constructed option for clingo
386
+ solution_list (dict): A dictionnary of all found solutions
387
+ search_mode (str): Optimization selected for the search (submin-enumeration / minimze-enumeration)
388
+ asp_files (list, optional): List of needed ASP files to solve ASP with Clyngor
389
+
390
+ Returns:
391
+ solution_list (dict): a dictionnary of all found solutions
392
+ """
393
+ transfers_complete=""
394
+ transf_short=""
395
+ trans_solution_list=None
396
+
397
+ if self.ground:
398
+ models = clyngor.solve_from_grounded(self.grounded, options=construct_option,
399
+ time_limit=self.time_limit, nb_model=self.number_solution).discard_quotes.by_predicate
400
+ else:
401
+ models = clyngor.solve(files=asp_files, options=construct_option,
402
+ time_limit=self.time_limit, nb_model=self.number_solution).discard_quotes.by_predicate
403
+ self.get_message("command")
404
+ logger.print_log(f'{models.command}', 'debug')
405
+ idx = 1
406
+ m = models
407
+ models_list = list(m).copy()
408
+ size_answers = len(models_list)
409
+ if size_answers != 0:
410
+ for model in models_list:
411
+ seeds = [args[0] for args in model.get('seed', ())]
412
+ seeds_full=list(model.get('seed', ()))
413
+ seeds=list(sorted(seeds))
414
+ size = len(seeds)
415
+
416
+ # In order to reuse this solve_enumeration function for global community mode,
417
+ # meaning a subset minimal on both seeds and transfers, we need to add the retrieving
418
+ # and completing messages here
419
+ # This allow us to not rewrite the full function on reasoningcom.py
420
+ if self.network.is_community:
421
+ transferred = model.get('transferred', ())
422
+ transferred=list(sorted(transferred))
423
+ transf_short, transfers_complete, trans_solution_list = self.get_transfers_info(transferred)
424
+ message = color.cyan_light + f"Answer: {idx}{color.reset} ({size} seeds{transf_short}) \n"
425
+ self.print_answer(message, seeds, seeds_full, transfers_complete)
426
+ solution_list, _ = self.complete_solutions(solution_list, 'model_'+str(idx), size, seeds,
427
+ trans_solution_list)
428
+ self.network.add_result_seeds('REASONING', search_mode, 'model_'+str(idx), size, seeds, transferred_list=trans_solution_list)
429
+ idx += 1
430
+ else:
431
+ logger.print_log('Unsatisfiable problem', "error")
432
+ return solution_list
433
+
434
+
435
+
436
+ def construct_string_option(self, full_option:list, use_clingo_constant:bool=True):
437
+ """Construct the string containing options for clingo solving and complete the full_option list
438
+
439
+ Args:
440
+ full_option (list): List of all options for clingo solving
441
+ use_clingo_constant (bool, optional): Add value of clingo constant if needed. Defaults to True.
442
+
443
+ Returns:
444
+ str, list: construct_option, full_option
445
+ """
446
+ if not self.ground and use_clingo_constant:
447
+ full_option = self.clingo_constant + full_option
448
+ if self.optimum:
449
+ # This option is for possible seed given by user
450
+ # We need to optimise on both producible targets and seeds
451
+ if self.opt_prod_tgt is not None:
452
+ full_option[-1]=full_option[-1]+f",{self.opt_prod_tgt},{self.opt_size}"
453
+ else:
454
+ full_option[-1]=full_option[-1]+f",{self.opt_size}"
455
+
456
+ construct_option = ' '.join(full_option)
457
+ return construct_option, full_option
458
+
459
+