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
@@ -0,0 +1,753 @@
1
+ from time import time
2
+ from .network import Network
3
+ from .reasoning import Reasoning
4
+ from .file import delete, save, load_tsv, existing_file
5
+ from . import color, logger
6
+ from multiprocessing import Process, Queue
7
+ from os import path
8
+ from json import loads
9
+
10
+
11
+ USE_MULTIPROCESSING=True
12
+
13
+ ###################################################################
14
+ ################# Class Reasoning : herit Solver ##################
15
+ ###################################################################
16
+ class ComReasoning(Reasoning):
17
+ def __init__(self, run_mode:str, run_solve:str, network:Network,
18
+ time_limit_minute:float=None, number_solution:int=None,
19
+ clingo_configuration:str=None, clingo_strategy:str=None,
20
+ intersection:bool=False, union:bool=False,
21
+ temp_dir:str=None, short_option:str=None,
22
+ verbose:bool=False, community_mode:str=None,
23
+ partial_delete_supset:bool=False, all_transfers:bool=False,
24
+ not_shown_transfers:bool=False, limit_transfers:int=-1):
25
+ """Initialize Object ComReasoning, herit from Reasoning
26
+
27
+ Args:
28
+ run_mode (str): Running command used (full or target)
29
+ network (Network): Network constructed
30
+ time_limit_minute (float, optional): Time limit given by user in minutes. Defaults to None.
31
+ number_solution (int, optional): Limit number of solutions to find. Defaults to None.
32
+ clingo_configuration (str, optional): Configuration for clingo resolution. Defaults to None.
33
+ clingo_strategy (str, optional): Strategy for clingo resolution. Defaults to None.
34
+ intersection (bool, optional): Find the intersection of all solutions without limitation (give one solution). Defaults to False.
35
+ union (bool, optional): Find the union of all solutions without limitation (give one solution). Defaults to False.
36
+ minimize (bool, optional): Search the minimal carinality of solutions. Defaults to False.
37
+ subset_minimal (bool, optional): Search the subset minimal solutions. Defaults to False.
38
+ temp_dir (str, optional): Temporary directory for saving instance file and clingo outputs. Defaults to None.
39
+ short_option (str, optional): Short way to write option on filename. Defaults to None.
40
+ verbose (bool, optional): Set debug mode. Defaults to False.
41
+ """
42
+ super().__init__(run_mode, run_solve, network,
43
+ time_limit_minute, number_solution,
44
+ clingo_configuration, clingo_strategy,
45
+ intersection, union,
46
+ False, True, # Minimize=False, Subset minimal = True
47
+ temp_dir, short_option,
48
+ verbose,
49
+ community_mode, all_transfers) # 'global', 'bisteps', 'delsupset', 'all'
50
+ self.partial_delete_supset = partial_delete_supset
51
+ self.not_shown_transfers = not_shown_transfers
52
+ self.limit_transfers = limit_transfers
53
+ if self.limit_transfers != -1:
54
+ self.clingo_constant.append('-c')
55
+ self.clingo_constant.append(f'limit_transfers={self.limit_transfers}')
56
+
57
+ def search_seed(self):
58
+ """Define the asp files to use before performing parent seed_searching
59
+ """
60
+ self.asp_files.append(self.asp.ASP_SRC_COMMUNITY)
61
+ if self.limit_transfers != -1:
62
+ self.asp_files.append(self.asp.ASP_SRC_LIMIT_TRANSFERS)
63
+ match self.community_mode:
64
+ case "global"|"delsupset":
65
+ if not self.not_shown_transfers:
66
+ self.asp_files.append(self.asp.ASP_SRC_COM_HEURISTIC)
67
+ self.asp_files.append(self.asp.ASP_SRC_SHOW_TRANSFERS)
68
+ super().search_seed()
69
+
70
+ case "bisteps":
71
+ super().search_seed()
72
+
73
+
74
+
75
+ def kill_conditon(self, size_list_sol:int, current_timer:float, stats:dict):
76
+ """_summary_
77
+
78
+ Args:
79
+ solution_list (int): _description_
80
+ current_timer (float): _description_
81
+ stats_seeds (dict): _description_
82
+ """
83
+ # No limit on number of solution
84
+ no_limit_solution = False
85
+
86
+ if self.number_solution == 0:
87
+ no_limit_solution = True
88
+
89
+ return not (((size_list_sol < self.number_solution \
90
+ and not no_limit_solution) \
91
+ or no_limit_solution )\
92
+ and \
93
+ ((self.time_limit and float(current_timer) < float(self.time_limit))\
94
+ or not self.time_limit)\
95
+ and \
96
+ stats == None)
97
+
98
+
99
+
100
+ def solve_bisteps(self, full_option:list, solution_list:dict,
101
+ search_mode:str, step:str, asp_files:list=None,
102
+ queue:Queue=None, full_path:str=None):
103
+ """Solve enumeration for Reasoning Classic mode using Clyngor
104
+
105
+ Args:
106
+ full_option (list): A list of all options needed for clingo solving
107
+ solution_list (dict): A dictionnary of all found solutions
108
+ search_mode (str): Optimization selected for the search (submin-enumeration / minimze-enumeration)
109
+ step (str): Which seed solving mode (classic, filter, guess-check or guess-chekc-div)
110
+ asp_files (list, optional): List of needed ASP files to solve ASP with Clyngor.
111
+ queue (Queue, optional): Queue for multiprocessing program (managing time limit). Defaults to None.
112
+ full_path (str, optional): Full path of temporary solution file
113
+
114
+ Returns:
115
+ solution_list (dict): a dictionnary of all found solutions
116
+ """
117
+
118
+ cobra_flux=None
119
+ # Guess_check or Guess_check diversity mode
120
+ if "guess_check" in self.run_solve :
121
+ is_guess_check = True
122
+ else:
123
+ is_guess_check = False
124
+
125
+ match step:
126
+ case "classic":
127
+ mode = 'REASONING'
128
+ case "filter":
129
+ mode = 'REASONING FILTER'
130
+ case "guess_check" | "guess_check_div":
131
+ mode = self.get_gc_mode()
132
+
133
+ # When we check filter, we want the limit of number solutions
134
+ # on validating flux model, and not on finding model only
135
+ if step == "classic":
136
+ nb_seeds=self.number_solution
137
+ else:
138
+ nb_seeds=0
139
+
140
+ _,full_option_seeds = self.construct_string_option(full_option)
141
+ full_option_seeds = self.complete_option(full_option_seeds, nb_seeds)
142
+ ctrl_seeds = self.control_init(full_option_seeds, asp_files, is_guess_check)
143
+
144
+ # Transfer preparing clingo
145
+ # In this step we do not need to search again for seeds
146
+ if self.all_transfers:
147
+ nb_transfer=0
148
+ else:
149
+ nb_transfer=1
150
+
151
+ transfers_asp_files=self.get_transfers_asp_file()
152
+ transfers_asp_files.append(self.asp.ASP_SRC_SEED_EXTERNAL)
153
+ _,full_option_transfer = self.construct_string_option(full_option)
154
+ full_option_transfer = self.complete_option(full_option_transfer,nb_transfer)
155
+ ctrl_transfers = self.control_init(full_option_transfer, transfers_asp_files, is_guess_check)
156
+
157
+
158
+ self.get_message("command search seed")
159
+ idx = 1
160
+
161
+ current_timer=0
162
+ number_rejected=0
163
+ start_time=time()
164
+ # When stats appears, Therefore clingo has finished his solutions
165
+ # Condition on stats helps finish while loop, especially when
166
+ # no time_limit or no numbersolution is given, or when time limit
167
+ # is higher than the total time for clingo to compute
168
+ # or number solution is higher than all solutions found with clingo.
169
+ # The condition on stats avoid to loop again on solving again
170
+ stats_seeds=None
171
+
172
+ with ctrl_seeds.solve(yield_=True) as h_seeds:
173
+ for model_seeds in h_seeds:
174
+ print("\n")
175
+
176
+ seeds_set = model_seeds.symbols(shown=True)
177
+
178
+ seeds, seeds_full, _, _ = self.get_seeds_transfers(seeds_set)
179
+ # enforce seeds
180
+ for seed in seeds_set:
181
+ ctrl_transfers.assign_external(seed, True)
182
+
183
+ # Get transfer for fixed set of seeds
184
+ self.get_message("command search transfers")
185
+ with ctrl_transfers.solve(yield_=True) as h_transfers:
186
+ for model_transfers in h_transfers:
187
+ transfers_set = model_transfers.symbols(shown=True)
188
+ _, _, transferred, _ = self.get_seeds_transfers(transfers_set)
189
+ transf_short, trans_complete, trans_solution_list =self.get_transfers_info(transferred)
190
+
191
+ size_seeds = len(seeds)
192
+
193
+ if step == "classic":
194
+ self.network.add_result_seeds(mode, search_mode, 'model_'+str(idx), size_seeds, seeds, transferred_list=trans_solution_list)
195
+ keep_solution=True
196
+ else:
197
+ res = self.network.check_seeds(seeds, trans_solution_list)
198
+ if res[0]:
199
+ # valid solution
200
+ logger.print_log(f'CHECK Solution {size_seeds} seeds -> OK\n', 'debug')
201
+ cobra_flux=res[1]
202
+ self.network.add_result_seeds(mode, "Community bisteps", 'model_'+str(idx), size_seeds, seeds, flux_cobra=cobra_flux, transferred_list=trans_solution_list)
203
+ keep_solution=True
204
+ else:
205
+ logger.print_log(f'CHECK Solution {size_seeds} seeds -> KO\n', 'debug')
206
+ number_rejected +=1
207
+ keep_solution=False
208
+ current_timer = time() - start_time
209
+ # write all 100 rejected
210
+ # or write from 5 minute before finishing the process near to finish the process
211
+ if USE_MULTIPROCESSING and number_rejected%100 == 0 \
212
+ or (current_timer!=0 and current_timer > self.time_limit_minute*60 - 300):
213
+ self.temp_rejected(number_rejected, full_path)
214
+ #print(current_timer)
215
+ current_timer = time() - start_time
216
+ if keep_solution:
217
+ message = color.cyan_light + f"Answer: {idx}{color.reset} ({size_seeds} seeds{transf_short}) \n"
218
+ self.print_answer(message, seeds, seeds_full, trans_complete)
219
+ solution_list, solution_temp = self.complete_solutions(solution_list, 'model_'+str(idx), size_seeds, seeds, trans_solution_list,cobra_flux,number_rejected)
220
+ if USE_MULTIPROCESSING:
221
+ solution_temp.append(seeds_full)
222
+ save(full_path, "", solution_temp, "tsv", True)
223
+ idx+=1
224
+ current_timer = time() - start_time
225
+
226
+ if self.kill_conditon(len(solution_list), current_timer, stats_seeds):
227
+ ctrl_transfers.interrupt()
228
+ ctrl_transfers.cleanup()
229
+ ctrl_seeds.interrupt()
230
+
231
+ # deactivate enforced seeds
232
+ for seed in seeds_set:
233
+ ctrl_transfers.assign_external(seed, False)
234
+
235
+ stats_seeds = ctrl_seeds.statistics
236
+
237
+ if self.kill_conditon(len(solution_list), current_timer, stats_seeds):
238
+ ctrl_seeds.interrupt()
239
+ ctrl_seeds.cleanup()
240
+
241
+
242
+ if step != "classic":
243
+ logger.print_log(f'Rejected solution during process: {number_rejected} \n', "info")
244
+ else:
245
+ number_rejected=None
246
+
247
+ if not any(solution_list):
248
+ logger.print_log('Unsatisfiable problem', "error")
249
+
250
+ if USE_MULTIPROCESSING:
251
+ queue.put([self, solution_list, None, current_timer, number_rejected])
252
+ else:
253
+ return solution_list, number_rejected
254
+
255
+
256
+ def check_set_submin(self, seeds:list, dict_by_size_seeds:dict, max_size:int, solution_list:dict):
257
+ """Used for delete superset mode. Check if the subset minimal set of seeds found is a subset
258
+ of a previous solutions found or not. If it is, the other solution is deleted.
259
+ "Manual checking" of subset minimal solution
260
+
261
+ Args:
262
+ seeds (list): List of seed found (subset minimal solution)
263
+ dict_by_size_seeds (dict): A dictionnary of all set of seeds found, having as key the size of the set to limit the loops
264
+ max_size (int): The maximal value of the size found (higher key of dict_by_size_seeds dictionnary)
265
+ solution_list (dict): List of solutions into a dictionnary (key value is the name of the model)
266
+ """
267
+ size_sup=len(seeds)+1
268
+
269
+ while size_sup <= max_size:
270
+ if size_sup in dict_by_size_seeds:
271
+ list_do_delete=list()
272
+ for name, solution in dict_by_size_seeds[size_sup].items():
273
+ if set(seeds).issubset(set(solution["seeds"])):
274
+ list_do_delete.append(name)
275
+ del solution_list[name]
276
+ self.network.result_seeds
277
+ # when there is no set of seeds in the key of dictionnary we remove it
278
+ if len(dict_by_size_seeds[size_sup])==0:
279
+ del dict_by_size_seeds[size_sup]
280
+ for name in list_do_delete:
281
+ del dict_by_size_seeds[size_sup][name]
282
+ size_sup+=1
283
+ return solution_list
284
+
285
+
286
+ def complete_dict_solution(self, solution_list:dict, dict_by_size_seeds:dict, max_size:int, seeds:list, size:int, name:str, sol:dict):
287
+ """Used for delete superset mode. Complete the dictionnary (key = size) solution and list of by adding or deleting solutions
288
+ After checking if set of seeds is a subset of a previous set of seeds found.
289
+
290
+ Args:
291
+ solution_list (dict): List of solutions into a dictionnary (key value is the name of the model)
292
+ dict_by_size_seeds (dict): A dictionnary of all set of seeds found, having as key the size of the set to limit the loops
293
+ max_size (int): The maximal value of the size found (higher key of dict_by_size_seeds dictionnary)
294
+ seeds (list): List of seed found (subset minimal solution)
295
+ size (int): size of set of seeds
296
+ name (str): name of the model solution
297
+ sol (dict): Dictionnary of the curent solution
298
+
299
+ Returns:
300
+ dict, dict, int: solution_list, dict_by_size_seeds, max_size
301
+ """
302
+ if size not in dict_by_size_seeds:
303
+ dict_by_size_seeds[size]={name:sol}
304
+ # when the size is bigger than all previsous size, this is a new subset minimal and can't be
305
+ # a superset of previous because of ASP constraints on superset
306
+ if (max_size and size > max_size) or not max_size:
307
+ max_size=size
308
+ # when the size is not on the dictionnary, we can have 2 situations:
309
+ # 1. The size is the smallest
310
+ # 2. The size is between two sizes
311
+ # For each of these cases we have to check if the solution is not a subset min of all
312
+ # solution having a bigger size
313
+ else :
314
+ if not self.partial_delete_supset:
315
+ solution_list=self.check_set_submin(seeds, dict_by_size_seeds, max_size, solution_list)
316
+ # When the size is in the dictionnary, we add the solution on the list of solution of this size
317
+ # and we have to check if the solution is not a subset min of all solution having a bigger size
318
+ else:
319
+ dict_by_size_seeds[size][name] = sol
320
+
321
+ if not self.partial_delete_supset:
322
+ solution_list=self.check_set_submin(seeds, dict_by_size_seeds, max_size, solution_list)
323
+ return solution_list, dict_by_size_seeds, max_size
324
+
325
+
326
+ def solve_delete_superset(self, full_option:list, solution_list:dict,
327
+ step:str, asp_files:list,
328
+ queue:Queue=None, full_path:str=None):
329
+ """Solve the seed searching in delete superset mode. For each global subset minimal solution
330
+ (seeds and transfer subset min), the set of seeds and superset oft this set is forbidden for the
331
+ next clingo solve. Because of this specific mode, some solution can be found as subset minimal as
332
+ previous solution later, and a manual check into solutions is needed by adding new solution and removing
333
+ all previous solution containing the new solution found.
334
+ WARNING: This is the only mode where Filter and Guess-Chek are integrated into the function due to
335
+ the specific solving mode.
336
+
337
+ Args:
338
+ full_option (list): All Clingo option
339
+ solution_list (dict): _description_
340
+ step (str): step solving mode (classic, filter, guess_check, guess_check_div).
341
+ asp_files (list): List of needed ASP files to solve ASP (Clingo package)
342
+ queue (Queue, optional): Queue for multiprocessing program (managing time limit). Defaults to None.
343
+ full_path (str, optional): Full path of temporary solution file. Defaults to None.
344
+
345
+ Returns:
346
+ _type_: _description_
347
+ """
348
+ all_time_solve = 0
349
+ all_time_ground = 0
350
+ dict_by_size_seeds=dict()
351
+ max_size=None
352
+ avoided = []
353
+ stats = None
354
+
355
+ _,full_option = self.construct_string_option(full_option)
356
+ full_option = self.complete_option(full_option,0)
357
+
358
+ if "guess_check" in self.run_solve :
359
+ is_guess_check = True
360
+ else:
361
+ is_guess_check = False
362
+
363
+ ctrl = self.control_init(full_option, asp_files, is_guess_check)
364
+ solution_idx = 1
365
+ number_rejected = 0
366
+ keep_solution = True
367
+
368
+ match step:
369
+ case "classic":
370
+ mode = 'REASONING'
371
+ case "filter":
372
+ mode = 'REASONING FILTER'
373
+ case "guess_check" | "guess_check_div":
374
+ mode = self.get_gc_mode()
375
+
376
+ start_time=time()
377
+ current_timer=0
378
+ while not self.kill_conditon(len(solution_list), current_timer, None):
379
+ with ctrl.solve(yield_=True) as h:
380
+ seeds = list()
381
+ transferred = list()
382
+ sol=dict()
383
+ cobra_flux=None
384
+ for model in h:
385
+ atoms = model.symbols(shown=True)
386
+
387
+ seeds, seeds_full, transferred, seed_complete_constraints = self.get_seeds_transfers(atoms)
388
+ transf_short, trans_complete, trans_solution_list =self.get_transfers_info(transferred)
389
+
390
+ size = len(seeds)
391
+
392
+ if step == "classic":
393
+ keep_solution=True
394
+ #Filter, Guess Check ou Guess Check Diversity
395
+ else :
396
+ res = self.network.check_seeds(seeds, trans_solution_list)
397
+ if res[0]:
398
+ # valid solution
399
+ logger.print_log(f'CHECK Solution {size} seeds -> OK\n', 'debug')
400
+ cobra_flux=res[1]
401
+ keep_solution=True
402
+ else:
403
+ logger.print_log(f'CHECK Solution {size} seeds -> KO\n', 'debug')
404
+ number_rejected +=1
405
+ keep_solution=False
406
+ current_timer = time() - start_time
407
+ # write all 100 rejected
408
+ # or write from 5 minute before finishing the process near to finish the process
409
+ if USE_MULTIPROCESSING and number_rejected%100 == 0 \
410
+ or (current_timer!=0 and current_timer > self.time_limit_minute*60 - 300):
411
+ self.temp_rejected(number_rejected, full_path)
412
+ keep_solution=False
413
+
414
+ current_timer = time() - start_time
415
+ if self.kill_conditon(len(solution_list), current_timer, None):
416
+ ctrl.interrupt()
417
+
418
+ if keep_solution:
419
+ if seeds:
420
+ name = 'model_' + str(solution_idx)
421
+
422
+ sol["seeds"] = set(seeds)
423
+ sol["seeds_full"]=seeds_full
424
+ sol["transf_short"]=transf_short
425
+ sol["trans_complete"]=trans_complete
426
+ else:
427
+ if not transferred:
428
+ sol["transf_short"] = sol["trans_complete"] = ""
429
+ trans_solution_list = list()
430
+ else:
431
+ sol["transf_short"]=transf_short
432
+ sol["trans_complete"]=trans_complete
433
+ if not self.partial_delete_supset:
434
+ solution_list, dict_by_size_seeds, max_size = self.complete_dict_solution(solution_list, dict_by_size_seeds, max_size, seeds, size, name, sol)
435
+ else:
436
+ message = color.cyan_light + f"Answer: {solution_idx}{color.reset} ({size} seeds{transf_short})\n"
437
+ self.print_answer(message, seeds, seeds_full, trans_complete)
438
+ self.network.add_result_seeds(mode, "Community delete superset of seeds", 'model_'+str(solution_idx), size, seeds, flux_cobra=cobra_flux, transferred_list=trans_solution_list)
439
+ break
440
+
441
+ if not seeds:
442
+ break
443
+
444
+ if keep_solution:
445
+ # Sum all grounding time together and all solving time together
446
+
447
+ #Save solution list as found (without deletion)
448
+ solution_list, solution_temp = self.complete_solutions(solution_list, name, size, seeds, trans_solution_list,cobra_flux,number_rejected)
449
+ if USE_MULTIPROCESSING:
450
+ solution_temp.append(seeds_full)
451
+ save(full_path, "", solution_temp, "tsv", True)
452
+ solution_idx +=1
453
+
454
+ stats = ctrl.statistics
455
+ total_time = stats["summary"]["times"]["total"]
456
+ time_solve = stats["summary"]["times"]["solve"]
457
+ time_ground = total_time - time_solve
458
+
459
+ all_time_solve += float(time_solve)
460
+ all_time_ground += float(time_ground)
461
+
462
+ current_timer = time() - start_time
463
+ if self.kill_conditon(len(solution_list), current_timer, None):
464
+
465
+ ctrl.interrupt()
466
+ ctrl.cleanup()
467
+ else:
468
+ # exclude solution and its supersets
469
+ ctrl.add("skip_seed_and_supset", [], seed_complete_constraints)
470
+ ctrl.ground([("skip_seed_and_supset",[])])
471
+
472
+ match step:
473
+ case "guess_check" | "guess_check_div":
474
+ ctrl, avoided= self.guess_check_constraints(ctrl, atoms, seeds, avoided)
475
+
476
+ if not self.partial_delete_supset:
477
+ solution_list = self.add_print_solution(solution_list, dict_by_size_seeds, mode, cobra_flux)
478
+
479
+ if step != "classic":
480
+ logger.print_log(f'Rejected solution during process: {number_rejected} \n', "info")
481
+ else:
482
+ number_rejected=None
483
+
484
+ if USE_MULTIPROCESSING:
485
+ queue.put([self, solution_list, all_time_ground, all_time_solve, number_rejected])
486
+ else:
487
+ return solution_list, number_rejected
488
+
489
+
490
+
491
+ def add_print_solution(self,solution_list:dict, dict_by_size_seeds:dict, mode:str, flux_cobra:float=None):
492
+ """Used for delete superset mode. The solution are printed after manually checking solution to identify if they are
493
+ subset minimal of previous found solution and finding the defined number of solutions wanted. The model name (solution)
494
+ are therefore reviewed. The solution are both printed and added as answer to the Network object.
495
+
496
+ Args:
497
+ solution_list (dict): List of solutions into a dictionnary (key value is the name of the model)
498
+ dict_by_size_seeds (dict): A dictionnary of all set of seeds found, having as key the size of the set to limit the loops
499
+ mode (str): The string solving mode (Classic, filter, guess_check, guess_check diversty)
500
+ flux_cobra (float, optional): The obra flux. Defaults to None.
501
+
502
+ Returns:
503
+ dict: new_solution_list
504
+ """
505
+
506
+ solution_idx = 1
507
+ new_solution_list=dict()
508
+ for name, solution in solution_list.items():
509
+ new_name = 'model_'+str(solution_idx)
510
+ new_solution_list[new_name]=solution
511
+ size = int(solution[1])
512
+ seeds = solution[3]
513
+ trans_solution_list = solution[5]
514
+
515
+ transf_short = dict_by_size_seeds[size][name]["transf_short"]
516
+ seeds_full = dict_by_size_seeds[size][name]["seeds_full"]
517
+ trans_complete = dict_by_size_seeds[size][name]["trans_complete"]
518
+
519
+ message = color.cyan_light + f"Answer: {solution_idx}{color.reset} ({size} seeds{transf_short})\n"
520
+ self.print_answer(message, seeds, seeds_full, trans_complete)
521
+ self.network.add_result_seeds(mode, "Community delete superset of seeds", new_name, size, seeds, flux_cobra=flux_cobra, transferred_list=trans_solution_list)
522
+
523
+ solution_idx +=1
524
+ return new_solution_list
525
+
526
+
527
+
528
+ def get_solution_from_temp_com(self, unsat:bool, full_path:str, step:str, mode:str):
529
+ """Get the solution written in temporary file during execution fo seed searching while using
530
+ multiprocessing.
531
+
532
+ Args:
533
+ unsat (bool): Determine if the model is unsat
534
+ full_path (str): Path of temporary file
535
+ step (str): step solving mode (classic, filter, guess_check, guess_check_div).
536
+
537
+ Returns:
538
+ List: list of solutions
539
+ """
540
+ solution_list = dict()
541
+ transferred_list = None
542
+
543
+ dict_by_size_seeds=dict()
544
+ max_size=None
545
+ number_rejected=None
546
+ if not unsat and existing_file(full_path):
547
+ # in case of enumeration it is needed to get the results back from the saved temporary
548
+ # file which is saved during the called
549
+ column_len = 7
550
+ try:
551
+ temp_list = load_tsv(full_path)
552
+ for solution in temp_list:
553
+ if len(solution) == column_len:
554
+ # some line has no data value only the number of rejected solution
555
+ if solution[0]:
556
+ name = solution[0]
557
+ size = int(solution[1])
558
+ seeds = solution[2].replace(" ", "")
559
+ seeds = seeds.replace("\'", "")
560
+ seeds_list = seeds[1:-1].split(',')
561
+ sol=dict()
562
+ sol["seeds"] = set(seeds)
563
+ seeds_full = solution[6].replace("[[", "[")
564
+ seeds_full = seeds_full.replace("]]", "]")
565
+ seeds_full = seeds_full.replace("String('", "'")
566
+ seeds_full = seeds_full.replace("')", "'")
567
+ seeds_full = eval(seeds_full)
568
+ sol["seeds_full"] = seeds_full
569
+
570
+ sol_details = ["size", solution[1]] + \
571
+ ["Set of seeds",seeds_list]
572
+
573
+ transferred_list = eval(solution[5])
574
+ sol_details += ["Set of transferred", transferred_list]
575
+
576
+ flux_cobra = loads(solution[4].replace("'",'"'))
577
+ sol += ["Cobra flux", flux_cobra]
578
+
579
+ transf_short, trans_complete, _ =self.get_transfers_info(transferred_list)
580
+
581
+ sol["transf_short"]=transf_short
582
+ sol["trans_complete"]=trans_complete
583
+
584
+ solution_list[solution[0]] = sol_details
585
+
586
+ solution_list, dict_by_size_seeds, max_size = self.complete_dict_solution(solution_list, dict_by_size_seeds, max_size, seeds, size, name, sol)
587
+
588
+ solution_list=self.check_set_submin(seeds, dict_by_size_seeds, max_size, solution_list)
589
+ #get the last occurence for rejected solutions number
590
+ number_rejected = solution[3]
591
+
592
+ except Exception as e:
593
+ logger.print_log(f"An error occured while reading temporary file\n {full_path}:\n {e}", 'error')
594
+
595
+ if any(solution_list):
596
+ solution_list = self.add_print_solution(solution_list, dict_by_size_seeds, mode, flux_cobra)
597
+
598
+ if step != "classic":
599
+ logger.print_log(f'Rejected solution during process: at least {number_rejected} \n', 'info')
600
+
601
+ delete(full_path)
602
+ return solution_list, number_rejected
603
+
604
+
605
+ def run_multiprocess(self, full_option:list, solution_list:dict,
606
+ step:str, asp_files:list, timer:dict,
607
+ output_type:str,suffix:str, search_mode:str=None):
608
+ """_summary_
609
+
610
+ Args:
611
+ full_option (list): _description_
612
+ solution_list (dict): _description_
613
+ step (str): _description_
614
+ asp_files (list): _description_
615
+ timer (dict): _description_
616
+ output_type (str): _description_
617
+ suffix (str): _description_
618
+ search_mode (str,Optional): _description_. Defaults to None.
619
+ """
620
+ queue = Queue()
621
+ full_path = path.join(self.temp_dir,f"{self.temp_result_file}.tsv")
622
+ start = time()
623
+ if self.community_mode == "bisteps":
624
+ p = Process(target=self.solve_bisteps(full_option, solution_list, search_mode,
625
+ step, asp_files, queue, full_path))
626
+ elif self.community_mode == "delsupset":
627
+ p = Process(target=self.solve_delete_superset(full_option, solution_list,
628
+ step, asp_files, queue, full_path))
629
+ p.start()
630
+ try:
631
+ # the time out limit is added here
632
+ obj, solution_list, time_ground, time_solve, number_rejected = queue.get(timeout=self.time_limit)
633
+ self.network.result_seeds = obj.network.result_seeds
634
+ delete(full_path)
635
+ except:
636
+ time_process=time() - start
637
+ time_ground = time_solve = -1
638
+ unsat = False
639
+ time_out = False
640
+ if not self.time_limit or time_process < self.time_limit:
641
+ unsat = True
642
+ else:
643
+ time_out = True
644
+ if time_out:
645
+ logger.print_log(f'Time out: {self.time_limit_minute} min expired', "error")
646
+
647
+ match step:
648
+ case "classic":
649
+ mode = 'REASONING'
650
+ case "filter":
651
+ mode = 'REASONING FILTER'
652
+ case "guess_check":
653
+ mode = 'REASONING GUESS-CHECK'
654
+ case "guess_check_div":
655
+ mode = 'REASONING GUESS-CHECK DIVERSITY'
656
+ solution_list, number_rejected = self.get_solution_from_temp_com(unsat, full_path, step, mode)
657
+
658
+ p.terminate()
659
+ queue.close()
660
+
661
+ if not any(solution_list):
662
+ logger.print_log('Unsatisfiable problem', "error")
663
+
664
+ if time_ground != None:
665
+ if time_ground != -1:
666
+ timer["Grounding time"] = round(time_ground, 3)
667
+ else:
668
+ timer["Grounding time"] = "Time out"
669
+ results=self.get_constructed_result(time_solve, timer, dict(), solution_list, number_rejected)
670
+ self.output[output_type+suffix] = results
671
+
672
+
673
+ def solve(self, search_mode:str, timer:dict, asp_files:list=None, step:str="classic"):
674
+ """Solve the seed searching using the launch mode
675
+
676
+ Args:
677
+ search_mode (str, optional): Describe the launch mode.
678
+ timer (dict): Timer dictionnary containing grouding time
679
+ """
680
+ solution_list = dict()
681
+ full_option, _, _, output_type = self.get_solutions_infos(search_mode)
682
+
683
+ match self.community_mode:
684
+ # When a subset minimal is done on global mode, meaning on both seeds and transfers together
685
+ # we can use the solving from the parent (class Reasoning) and therefore the filter and
686
+ # guess check fucntions from the parent.
687
+ case "global" :
688
+ super().solve(search_mode,timer,asp_files,step)
689
+
690
+ # To use the bisteps mode, meaning first we do a subset minimal on seeds then we find one
691
+ # subset minimal on transfers, we need a new solving modes
692
+ case "bisteps":
693
+ suffix=self.get_suffix(step)
694
+ match search_mode, step:
695
+ # CLASSIC MODE (NO FILTER, NO GUESS-CHECK)
696
+ case "minimize-enumeration" | "minimize-one-model", _:
697
+ logger.print_log("No minimisation in community", "warning")
698
+
699
+ case "submin-enumeration", _:
700
+ logger.print_log("SOLVING...\n", "info")
701
+ if USE_MULTIPROCESSING:
702
+ self.run_multiprocess(full_option, solution_list, step, asp_files, timer, output_type, suffix, search_mode)
703
+ else :
704
+ time_solve = time()
705
+ solution_list, number_rejected = self.solve_bisteps(full_option, solution_list,
706
+ search_mode,step, asp_files)
707
+ time_solve = time() - time_solve
708
+
709
+ results=self.get_constructed_result(time_solve, timer, dict(), solution_list, number_rejected)
710
+ self.output[output_type+suffix] = results
711
+
712
+
713
+
714
+ # To use the delsupset mode, meaning we solve and subset min on both seed and transfers, then add
715
+ # a constraint to forbid all superset of set of seeds (only) found, we need to to id step by step
716
+ # and dynamically add the constraint on superset. We need a new solving mode, getting the first subsetmin
717
+ # solution in seed, adding a constraint and redoing this until having the number of solution requested.
718
+ # Doing this might select some set as subset min first (mixing seeds and transfers) but after find a subsetmin
719
+ # of seed (but different transfers) that will be a subsetmin of first solution. It is needed to take into account
720
+ # manually this cases and delete the previous solution of results.
721
+ # example :
722
+ # solution 1: seeds are A, B, C , transfers is D
723
+ # solution 2: seeds are A, B, transfers D, E
724
+ # both are global subset min, but solution 2 is subset min of solution 1 regarding only seeds
725
+ case "delsupset":
726
+ suffix=self.get_suffix(step)
727
+ match search_mode, step:
728
+ # CLASSIC MODE (NO FILTER, NO GUESS-CHECK)
729
+ case "minimize-enumeration" | "minimize-one-model", _:
730
+ logger.print_log("No minimisation in community", "warning")
731
+
732
+ # Because of specific analyse of solution with "manual deletion of previous superset"
733
+ # All solving mode are in one function (classic, filter, guess check and gues check div)
734
+ # unlike all other community modes (or signle network modes)
735
+ case "submin-enumeration", "classic" | "filter" | "guess_check" | "guess_check_div":
736
+ logger.print_log("SOLVING...\n", "info")
737
+
738
+ # Time limit may not work well if no solutions found within time limit
739
+ # But save time from writting into temporary file
740
+ if USE_MULTIPROCESSING:
741
+ self.run_multiprocess(full_option, solution_list, step, asp_files, timer, output_type, suffix)
742
+
743
+ else :
744
+ time_solve = time()
745
+ solution_list, number_rejected = self.solve_delete_superset(full_option, solution_list,
746
+ step, asp_files)
747
+ time_solve = time() - time_solve
748
+
749
+ results=self.get_constructed_result(time_solve, timer, dict(), solution_list, number_rejected)
750
+ self.output[output_type+suffix] = results
751
+
752
+
753
+