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,791 @@
1
+ from .solver import Solver
2
+ from .network import Network
3
+ from multiprocessing import Process, Queue
4
+ from .file import save, delete, write_instance_file, load_tsv, existing_file
5
+ import clingo
6
+ from . import color, logger
7
+ from os import path
8
+ import random
9
+ from time import time
10
+ from json import loads
11
+
12
+ ###################################################################
13
+ ############# Class HybridReasoning : herit Solver ################
14
+ ###################################################################
15
+ class HybridReasoning(Solver):
16
+ def __init__(self, run_mode:str, network:Network,
17
+ time_limit_minute:float=None, number_solution:int=None,
18
+ clingo_configuration:str=None, clingo_strategy:str=None,
19
+ intersection:bool=False, union:bool=False,
20
+ minimize:bool=False, subset_minimal:bool=False,
21
+ temp_dir:str=None, short_option:str=None, run_solve:str=None,
22
+ verbose:bool=False, community_mode:str=None,all_transfers:bool=False):
23
+ """Initialize Object HybridReasoning, herit from Solver
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)
43
+
44
+ self.all_transfers = all_transfers
45
+
46
+
47
+ ######################## METHODS ########################
48
+
49
+ def control_init(self, full_option:list, asp_files:list, is_guess_check:bool=False):
50
+ """Initiate Clingo control for package Clingo
51
+
52
+ Args:
53
+ full_option (list): All Clingo option
54
+ asp_files (list): List of needed ASP files to solve ASP (Clingo package)
55
+ is_guess_check (bool, optional): Determine if it is a Guess Check (True) or a Filter (Fale).
56
+ Defaults to False.
57
+
58
+ Returns:
59
+ ctrl (clingo.Control): Return Clingo control for solving
60
+ """
61
+ if "--warn=none" not in full_option:
62
+ full_option.append("--warn=none")
63
+ ctrl = clingo.Control(full_option)
64
+
65
+ for file in asp_files:
66
+ ctrl.load(file)
67
+
68
+ ctrl.ground([("base",[])])
69
+ if self.diversity and is_guess_check:
70
+ ctrl.add("diversity", [], """
71
+ #program diversity.
72
+ #heuristic new_seed(M) : avoidseed(M). [10,false]
73
+ #heuristic new_seed(M). [1,false] % subset
74
+ #external avoidseed(M) : metabolite(M,_).
75
+ """)
76
+ ctrl.ground([("diversity",[])])
77
+
78
+ self.get_message("command")
79
+ logger.print_log('clingo ' + ' '.join(full_option) + ' ' + ' '.join(asp_files), 'debug')
80
+ return ctrl
81
+
82
+
83
+ def get_constructed_result(self, time_solve:float, timer:dict, results:dict, solution_list:dict, number_rejected:int):
84
+ """Complete and structure the resulted dictionnary
85
+
86
+ Args:
87
+ time_solve (float): Solving time
88
+ timer (dict): dictionnary of timers (with other timer if exists)
89
+ results (dict): final dictionnary of results
90
+ solution_list (dict): Dictionnary containing the models resulted for one mode (reasoning, filter, guess-check ...)
91
+ number_rejected (int): Used for hybrid-cobra modes (filter, guess-check, guesscheck div)
92
+
93
+ Returns:
94
+ dict: results
95
+ """
96
+ if time_solve != -1:
97
+ timer["Solving time"] = round(time_solve, 3)
98
+ else:
99
+ timer["Solving time"] = "Time out"
100
+ results["Timer"] = timer.copy()
101
+ results['solutions'] = solution_list
102
+ if number_rejected is not None:
103
+ results['rejected'] = number_rejected
104
+ return results
105
+
106
+
107
+ def save_seeds_tmp(self, seeds_fact:str, idx:int):
108
+ """Save seeds as asp fact in temporary file for bistep mode in community after the first step which find and subset min only on seeds
109
+ to be able to retrieve the seeds as an asp file and do the second step which found the first solution of subsetminimal transfers with
110
+ those saved seeds
111
+
112
+ Args:
113
+ seeds_fact (str): seeds converted into asp facts
114
+ idx (int): index of the soulution (solution number idx)
115
+
116
+ Returns:
117
+ str: seeds_temp_file
118
+ """
119
+ # create a temporary seeds instance file
120
+ seeds_temp_file=f"seeds_model_{idx}_{self.temp_result_file}.lp"
121
+ seeds_temp_file=path.join(self.temp_dir,seeds_temp_file)
122
+ write_instance_file(seeds_temp_file, seeds_fact)
123
+ return seeds_temp_file
124
+
125
+
126
+ def get_transfers_info(self, list_transferred:list):
127
+ """From list of transferred, get datas and structure them into a dictionnary for output result file
128
+
129
+ Args:
130
+ list_transferred (list): List of transferred metabolite
131
+
132
+ Returns:
133
+ str,str,list: transf_short (size of transfers), trans_complete (line of printed transfers), trans_solution_list
134
+ """
135
+ size_transf = len(list_transferred)
136
+ trans_complete=None
137
+ trans_solution_list=list()
138
+ if not self.network.is_community:
139
+ transf_short = ""
140
+ elif size_transf>0:
141
+ transf_short = f" and {size_transf} transfers"
142
+ trans_complete = f"\n Transfers {color.cyan_dark}|{color.reset} From {color.cyan_dark}|{color.reset} To\n"
143
+ trans_complete += f"--------------{color.cyan_dark}|{color.reset}--------------{color.cyan_dark}|{color.reset}--------------\n"
144
+ for meta in list_transferred:
145
+ # When we get the data from temp file, it is already formated as dictionnary
146
+ if type(meta) == dict:
147
+ dict_transf=meta
148
+ else:
149
+ dict_transf=dict()
150
+ dict_transf["Metabolite"] = str(meta[0]).replace('"','')
151
+ dict_transf["From"] = str(meta[1]).replace('"','')
152
+ dict_transf["To"] = str(meta[2]).replace('"','')
153
+ dict_transf["ID from"] = str(meta[3]).replace('"','')
154
+ dict_transf["ID to"] = str(meta[4]).replace('"','')
155
+ trans_complete += f'{dict_transf["Metabolite"]} {color.cyan_dark}|{color.reset} {dict_transf["From"]} {color.cyan_dark}|{color.reset} {dict_transf["To"]}\n'
156
+ trans_solution_list.append(dict_transf)
157
+ else:
158
+ transf_short = f" and no transfers"
159
+
160
+ return transf_short, trans_complete, trans_solution_list
161
+
162
+
163
+ def complete_option(self, full_option:list, nb_sol:int):
164
+ """Convert the list of clingo options into string and complete it with the wanted number of solutions.
165
+ The number of solutions depends on if we are minimizing the solution, or if we want to find the only the first
166
+ subset minimal of solutions.
167
+
168
+ Args:
169
+ full_option (list): List of clingo options
170
+ nb_sol (int): number of solutions to ask to clingo
171
+
172
+ Returns:
173
+ str: complete_option
174
+ """
175
+ complete_option=full_option.copy()
176
+ complete_option.append(f'-n {nb_sol}')
177
+ return complete_option
178
+
179
+
180
+ def get_transfers_asp_file(self):
181
+ """Remove seed solving files and get transfers file for asp solving.
182
+
183
+ Returns:
184
+ list: list of path to asp files
185
+ """
186
+ transf_asp_files=self.asp_files.copy()
187
+ transf_asp_files.remove(self.asp.ASP_SRC_SEED_SOLVING)
188
+ transf_asp_files.remove(self.asp.ASP_SRC_SHOW_SEEDS)
189
+ transf_asp_files.append(self.asp.ASP_SRC_ATOM_TRANSF)
190
+ transf_asp_files.append(self.asp.ASP_SRC_SHOW_TRANSFERS)
191
+ return transf_asp_files
192
+
193
+
194
+ def convert_seeds_to_fact(self, args:tuple, seeds_fact:str, is_supsetconstraint:bool = False):
195
+ """Convert either seeds into asp seeds fact (bisteps mode) or constraints from seeds
196
+ to forbid set of seed as solution for next solve and constraints for superset of seeds
197
+ to frobid solution including set of seeds (delsupset mode)
198
+
199
+ Args:
200
+ args (tuple): Atom argument from clingo
201
+ seeds_fact (str): Seeds asp fact convert to string to complete
202
+
203
+ Returns:
204
+ str: seeds_fact
205
+ """
206
+ metabolite_id=str(args[0]).replace('"','')
207
+ metabolite_flag=str(args[1]).replace('"','')
208
+ metabolite_name=str(args[2]).replace('"','')
209
+ species=str(args[3]).replace('"','')
210
+ match self.community_mode:
211
+ case "bisteps":
212
+ seeds_fact += f'\nseed("{metabolite_id}","{metabolite_flag}","{metabolite_name}","{species}").'
213
+ # in case of delete superset, we do not need to create seeds fact but we need to add constraints
214
+ case "delsupset":
215
+ # Superset of set of seeds to forget for next search
216
+ if is_supsetconstraint:
217
+ seeds_fact += f', seed("{metabolite_id}","{metabolite_flag}","{metabolite_name}","{species}"), X!="{metabolite_id}"'
218
+ # Set of seeds to forget for next search
219
+ else:
220
+ if "seed" in seeds_fact:
221
+ seeds_fact += ', '
222
+ seeds_fact += f'seed("{metabolite_id}","{metabolite_flag}","{metabolite_name}","{species}")'
223
+ return seeds_fact
224
+
225
+
226
+ def get_seeds_transfers(self, atoms):
227
+ """Get Seeds solution from atoms for all modes and transfers for communtiy mode.
228
+ For delete superset mode, can create the needed constraints (forbid set of seeds and its super set for next search).
229
+ Return a list of seeds (onlyname), but also list of full seeds (all data from seeds atom), a list of transferred metabolites
230
+ if needed and the complete contraints to add to clingo.
231
+
232
+ Args:
233
+ atoms: clingo atoms (returned by solver)
234
+
235
+ Returns:
236
+ list, list, list, str: seeds, seeds_full, transferred, seed_complete_constraints
237
+ """
238
+ seeds = list()
239
+ seeds_full = list()
240
+ transferred = list()
241
+ seed_constraints=":- "
242
+ seed_superset_constraints=":- seed(X,_,_,_)"
243
+ seed_complete_constraints=""
244
+ transfer_constraints=""
245
+ # For single network search, there is only seed, not transfer
246
+ if not self.network.is_community:
247
+ for a in atoms:
248
+ if a.name == "seed":
249
+ seeds.append(a.arguments[0].string)
250
+ seeds=list(sorted(seeds))
251
+ else:
252
+ for a in atoms:
253
+ if a.name == "seed":
254
+ seeds.append(a.arguments[0].string)
255
+ seeds_full.append(a.arguments)
256
+ if self.community_mode=="delsupset":
257
+ seed_constraints = self.convert_seeds_to_fact(a.arguments, seed_constraints)
258
+ seed_superset_constraints = self.convert_seeds_to_fact(a.arguments, seed_superset_constraints,True)
259
+ elif a.name == "transferred":
260
+ transferred.append(a.arguments)
261
+ seeds=list(sorted(seeds))
262
+ transferred=list(sorted(transferred))
263
+ if self.all_transfers:
264
+ for transfer in transferred:
265
+ transfer_constraints+=f", transferred({transfer[0]},{transfer[1]},{transfer[2]},{transfer[3]},{transfer[4]})"
266
+ seed_complete_constraints = seed_constraints+transfer_constraints+".\n"+seed_superset_constraints+"."
267
+
268
+ return seeds, seeds_full, transferred, seed_complete_constraints
269
+
270
+
271
+ def complete_solutions(self, solution_list:dict, solution_name:str, size:int, seeds:list,
272
+ trans_solution_list:list=None, cobra_flux:dict=None, number_rejected:int=None):
273
+ """Complete the solutions ilst and the solution temporary list to save due to of multiprocessing.
274
+ This function is used for filter and guess_check function, but also for delete superset mode in community
275
+ which also use multiprocessing du create constraints while searching solution
276
+
277
+ Args:
278
+ solution_list (dict): Dictionnary of solutions by name (model idx)
279
+ solution_name (str): Current solution name
280
+ size (int): size of set of seeds
281
+ seeds (list): list of seeds
282
+ trans_solution_list (list): list of transfers solution
283
+ cobra_flux (dict, optional): Cobra flux found for solution if exists. Defaults to None.
284
+ number_rejected (int, optional): Number of rejected soution if exists. Defaults to None.
285
+
286
+ Returns:
287
+ dict, list: solution_list, solution_temp
288
+ """
289
+ solution_temp=None
290
+
291
+ seeds=list(sorted(seeds))
292
+ solution = ["size", size] + ["Set of seeds", seeds]
293
+ if self.network.is_community:
294
+ solution += ["Set of transferred", trans_solution_list]
295
+ # Solutions from filter and guess check
296
+ if cobra_flux or \
297
+ (self.network.is_community and self.community_mode!="global"):
298
+ if cobra_flux:
299
+ solution += ["Cobra flux", cobra_flux]
300
+ solution_temp = [solution_name, size, seeds, number_rejected, cobra_flux]
301
+ if self.network.is_community:
302
+ solution_temp.append(trans_solution_list)
303
+ solution_list[solution_name]=solution
304
+ return solution_list, solution_temp
305
+
306
+
307
+ def add_diversity(self, ctrl:clingo.Control, seeds:list, avoided:list):
308
+ """This function add diversity for the Gess Check mode by avoiding some metabolites
309
+ from previous solution. For each iteration, half of the avoided metabolites is
310
+ deleted randomly, and half of metabolites as seeds of the current solution is added randomly
311
+
312
+ Args:
313
+ ctrl (clingo.Control): Clingo Control initiated
314
+ seeds (list): List of seeds (one solution)
315
+ avoided (list): List of already avoided metabolites
316
+
317
+ Returns:
318
+ ctrl (clingo.Control), avoided (list): Return Clingo control for solving
319
+ and the new list of avoided metabolites for next iteration
320
+ """
321
+ forget = 50 # 0..100: percentage of heuristics to forget at each iteration
322
+
323
+ # tune heuristics for diversity
324
+ random.shuffle(avoided)
325
+ clue_to_forget = (len(avoided)*forget)//100
326
+ for a in avoided[:clue_to_forget]:
327
+ ctrl.assign_external(a, False)
328
+ avoided = avoided[clue_to_forget:]
329
+
330
+
331
+ random.shuffle(seeds)
332
+ seed_to_forget = (len(seeds)*forget)//100
333
+ seeds = seeds[seed_to_forget:]
334
+
335
+ clues = [clingo.Function("avoidseed", [clingo.String(s)]) for s in seeds]
336
+
337
+ for a in clues:
338
+ ctrl.assign_external(a, True)
339
+ avoided.extend(clues)
340
+
341
+ return ctrl, avoided
342
+
343
+ def get_solution_from_temp(self, unsat:bool, is_one_model:bool, full_path:str, suffix:str, search_mode:str):
344
+ """Get the solution written in temporary file during execution fo seed searching while using
345
+ multiprocessing.
346
+
347
+ Args:
348
+ unsat (bool): Determine if the model is unsat
349
+ is_one_model (bool): Determine if the model is the optimum finding model for minimize case
350
+ full_path (str): Path of temporary file
351
+ suffix (str): suffix to add for solution enumeration (filter or guess-check)
352
+ search_mode (str): search_mode needed to add to results (subset minimal or minimize)
353
+
354
+ Returns:
355
+ dict, str: list of solutions, number of rejected solution
356
+ """
357
+ solution_list = dict()
358
+ number_rejected = None
359
+ transferred_list = None
360
+ if not unsat and existing_file(full_path):
361
+ # in case of enumeration it is needed to get the results back from the saved temporary
362
+ # file which is saved during the called
363
+ column_len = 5
364
+ if self.network.is_community:
365
+ column_len = 6
366
+ if not is_one_model:
367
+ try:
368
+ temp_list = load_tsv(full_path)
369
+ for solution in temp_list:
370
+ if len(solution) == column_len:
371
+ # some line has no data value onlu the number of rejected solution
372
+ if solution[0]:
373
+ seeds = solution[2].replace(" ", "")
374
+ seeds = seeds.replace("\'", "")
375
+ seeds_list = seeds[1:-1].split(',')
376
+
377
+ sol = ["size", solution[1]] + \
378
+ ["Set of seeds",seeds_list]
379
+ if self.network.is_community:
380
+ transferred_list = eval(solution[5])
381
+ sol += ["Set of transferred", transferred_list]
382
+
383
+ cobra_dict = loads(solution[4].replace("'",'"'))
384
+ sol += ["Cobra flux", cobra_dict]
385
+ solution_list[solution[0]] = sol
386
+ #get the last occurence pf rejected solutions number
387
+ number_rejected = solution[3]
388
+ logger.print_log(f'Rejected solution during process: at least {number_rejected} \n', 'info')
389
+ except Exception as e:
390
+ logger.print_log(f"An error occured while reading temporary file\n {full_path}:\n {e}", 'error')
391
+
392
+ if any(solution_list):
393
+ for name in solution_list:
394
+ seeds = solution_list[name][3]
395
+ self.network.add_result_seeds('REASONING '+suffix, search_mode, name, len(seeds), seeds, transferred_list=transferred_list)
396
+ delete(full_path)
397
+ return solution_list, number_rejected
398
+
399
+
400
+
401
+ def solve_hybrid(self, step:str, full_option:list, asp_files:list, search_mode:str, is_one_model:bool):
402
+ """Solve hybrid-cobra mode depending step (filter, guess_check, guess_check_div)
403
+
404
+ Args:
405
+ step (str): Hybrid solving mode (filter, guess_check, guess_check_div)
406
+ full_option (list): List of clingo options
407
+ asp_files (list): list of path to asp files
408
+ search_mode (str): Search mode (minimize or subset minimal)
409
+ is_one_model (bool): Determine if the model is the optimum finding model for minimize case
410
+
411
+ Returns:
412
+ int, int, dict, str: time_solve, time_ground, solution_list, number_rejected
413
+ """
414
+ # api clingo doesn't have time_limit option
415
+ # to add a time out, it is needed to call the function into a process
416
+ queue = Queue()
417
+ start=time()
418
+ if step == "filter":
419
+ suffix = " FILTER"
420
+ full_path = path.join(self.temp_dir,f"{self.temp_result_file}.tsv")
421
+ p = Process(target=self.filter, args=(queue, full_option, asp_files, search_mode, full_path, is_one_model))
422
+ elif "guess_check" in step:
423
+ suffix = " GUESS-CHECK"
424
+ self.diversity=False
425
+ if step == "guess_check_div":
426
+ suffix += "-DIVERSITY"
427
+ self.diversity=True
428
+ full_path = path.join(self.temp_dir,f"{self.temp_result_file}.tsv")
429
+ p = Process(target=self.guess_check, args=(queue, full_option, asp_files, search_mode, full_path, is_one_model))
430
+
431
+ p.start()
432
+ try:
433
+ # the time out limit is added here
434
+ obj, solution_list, time_ground, time_solve, number_rejected = queue.get(timeout=self.time_limit)
435
+ #solution_list, number_rejected = self.get_solution_from_temp(unsat, is_one_model, full_path, suffix, search_mode)
436
+
437
+ # Because of the process, the object is not change (encapsulated and isolated)
438
+ # it is needed to give get the output object and modify the current object
439
+ if "minimize" in search_mode:
440
+ self.optimum_found = obj.optimum_found
441
+ self.optimum = obj.optimum
442
+ self.get_separate_optimum()
443
+ self.network.result_seeds = obj.network.result_seeds
444
+ if not is_one_model:
445
+ delete(full_path)
446
+ except:
447
+ time_process=time() - start
448
+ time_ground = time_solve = -1
449
+ unsat = False
450
+ time_out = False
451
+ if not self.time_limit or time_process < self.time_limit:
452
+ unsat = True
453
+ else:
454
+ time_out = True
455
+ if time_out:
456
+ logger.print_log(f'Time out: {self.time_limit_minute} min expired', "error")
457
+
458
+ solution_list, number_rejected = self.get_solution_from_temp(unsat, is_one_model, full_path, suffix, search_mode)
459
+ p.terminate()
460
+ queue.close()
461
+
462
+ if is_one_model:
463
+ if not self.optimum_found:
464
+ logger.print_log('Optimum not found', "error")
465
+ else:
466
+ if not any(solution_list):
467
+ logger.print_log('Unsatisfiable problem', "error")
468
+
469
+ return time_solve, time_ground, solution_list, number_rejected
470
+
471
+
472
+ def print_answer(self, message:str, seeds:list, seeds_full:list, trans_complete:str):
473
+ """Print into terminal the answer (set of seeds and transfers if exists)
474
+
475
+ Args:
476
+ message (str): Constructed message to print
477
+ seeds (list): list of seeds
478
+ seeds_full (list): list of seeds wit all data from asp atoms answer (species associated)
479
+ trans_complete (str): List of transfers with all data (metabolite, from, to)
480
+ """
481
+ if not self.network.is_community:
482
+ for s in seeds:
483
+ message += f"{s}, "
484
+ message=message.rstrip(', ')
485
+ else:
486
+ seeds_dict=dict()
487
+ seeds_full = sorted(seeds_full, key=lambda x: x[0])
488
+ for s in seeds_full:
489
+ species = str(s[3]).replace('"','')
490
+ seed = str(s[2]).replace('"','')
491
+ if species in seeds_dict.keys():
492
+ seeds_dict[species]+= f', {seed}'
493
+ else:
494
+ seeds_dict[species] = f'{seed}'
495
+ for key, value in sorted(seeds_dict.items()):
496
+ message += color.cyan_dark + f'{key}:' + color.reset
497
+ message += f' {value}'
498
+ message = message.rstrip(', ')
499
+ message += "\n"
500
+ if trans_complete is None:
501
+ trans_complete=color.yellow+"No transferred metabolites\n"+color.reset
502
+ message += trans_complete #+ "\n"
503
+ print(message)
504
+
505
+ def temp_rejected(self, number_rejected:int, full_path:str):
506
+ """Save temporary data for rejected number of solution when using cobra hybrid mode
507
+
508
+ Args:
509
+ number_rejected (int): _description_
510
+ full_path (str): Path of temporary file
511
+ """
512
+ solution_temp = [None, None, None, number_rejected, None]
513
+ if self.network.is_community:
514
+ solution_temp.append(None)
515
+ save(full_path, "", solution_temp, "tsv", True)
516
+
517
+ def filter(self, queue:Queue, full_option:list, asp_files:list, search_mode:str, full_path:str, is_one_model:bool=False):
518
+ """Filter mode. Find a solution with Clingo package, check if the solution has flux on objective reaction.
519
+ This function works with multiprocessing in order to manage time limit.
520
+ It does not interact with the solver, only filter the solutions.
521
+
522
+ Args:
523
+ queue (Queue): Queue for multiprocessing program (managing time limit)
524
+ full_option (list): All Clingo option
525
+ asp_files (list): List of needed ASP files to solve ASP (Clingo package)
526
+ search_mode (str): Optimization selected for the search (submin/minmize and enumeration/optimum)
527
+ full_path (str): Full path for temp file needed to get back solution when time out
528
+ is_one_model (bool, optional): Define if the solution we want is to fin the optimum when minimize is used (before enumration).
529
+ Defaults to False.
530
+ """
531
+ solution_list = dict()
532
+
533
+ no_limit_solution = False
534
+ if self.number_solution == 0:
535
+ no_limit_solution = True
536
+
537
+ full_option_seeds = self.complete_option(full_option,0)
538
+
539
+ ctrl = self.control_init(full_option_seeds, asp_files)
540
+ solution_idx = 1
541
+ number_rejected = 0
542
+ start_time=time()
543
+ with ctrl.solve(yield_=True) as h:
544
+ for model in h:
545
+ if (len(solution_list) < self.number_solution \
546
+ and not is_one_model and not no_limit_solution) \
547
+ or is_one_model or no_limit_solution:
548
+ atoms = model.symbols(shown=True)
549
+ seeds, seeds_full, transferred, _ = self.get_seeds_transfers(atoms)
550
+ size = len(seeds)
551
+ transf_short, trans_complete, trans_solution_list =self.get_transfers_info(transferred)
552
+
553
+ if not is_one_model:
554
+ res = self.network.check_seeds(seeds, trans_solution_list)
555
+ if res[0]:
556
+ # valid solution
557
+ logger.print_log(f'CHECK Solution {size} seeds -> OK\n', 'debug')
558
+
559
+ message = color.cyan_light + f"Answer: {solution_idx}{color.reset} ({size} seeds{transf_short})\n"
560
+ self.print_answer(message, seeds, seeds_full, trans_complete)
561
+ name = 'model_'+str(solution_idx)
562
+
563
+ solution_list, solution_temp = self.complete_solutions(solution_list, name, size, seeds,
564
+ trans_solution_list, res[1], number_rejected)
565
+
566
+ save(full_path, self.temp_dir, solution_temp, "tsv", True)
567
+ self.network.add_result_seeds('REASONING FILTER', search_mode, name, size, seeds, flux_cobra=res[1], transferred_list=trans_solution_list)
568
+ solution_idx +=1
569
+ else:
570
+ logger.print_log(f'CHECK Solution {size} seeds -> KO\n', 'debug')
571
+ number_rejected +=1
572
+ current_timer = time() - start_time
573
+ # write all 100 rejected
574
+ # or write from 5 minute before finishing the process near to finish the process
575
+ if number_rejected%100 == 0 \
576
+ or (current_timer!=0 and current_timer > self.time_limit_minute*60 - 300):
577
+ self.temp_rejected(number_rejected, full_path)
578
+ # This means we are in "is_one_model", we are searching for minimize
579
+ # there is no minimize with community mode
580
+ else:
581
+ res = self.network.check_seeds(seeds, transferred)
582
+ self.optimum=model.cost
583
+ self.get_separate_optimum()
584
+ name = 'model_one_solution'
585
+ solution_list, solution_temp = self.complete_solutions(solution_list, name, size, seeds,
586
+ trans_solution_list, res[1], number_rejected)
587
+ self.optimum_found = True
588
+ else:
589
+ break
590
+
591
+ logger.print_log(f'Rejected solution during process: {number_rejected} \n', "info")
592
+
593
+ stats = ctrl.statistics
594
+ total_time = stats["summary"]["times"]["total"]
595
+ time_solve = stats["summary"]["times"]["solve"]
596
+ time_ground = total_time - time_solve
597
+
598
+ # Because it is needed to get all answers from clingo to have optimum, we save it after
599
+ # No minimize in community mode
600
+ if is_one_model and self.optimum_found:
601
+ logger.print_log(f"Optimum found.", "info")
602
+ if self.network.is_subseed:
603
+ logger.print_log(f"Number of producible targets: {- self.opt_prod_tgt}", "info")
604
+ logger.print_log(f"Minimal size of seed set is {self.opt_size}\n", "info")
605
+ save(full_path, self.temp_dir, solution_temp, "tsv", True)
606
+ self.network.add_result_seeds('REASONING FILTER', search_mode, name, size, seeds, flux_cobra=res[1], transferred_list=trans_solution_list)
607
+
608
+ ctrl.cleanup()
609
+ ctrl.interrupt()
610
+ queue.put([self, solution_list, time_ground, time_solve, number_rejected])
611
+
612
+
613
+ def guess_check_constraints(self, ctrl, atoms, seeds:list, avoided:list):
614
+ """Add constraints to clingo controller for guess Check mode, and supplementary constraints for diversity mode.
615
+
616
+ Args:
617
+ ctrl: clingo controller to add constraints
618
+ atoms: clingo atoms
619
+ seeds (list): list of seeds
620
+ avoided (list): list of previous avoided seeds
621
+
622
+ Returns:
623
+ clingo controller, str, list: ctrl, mode, avoided (list of new avoided seeds)
624
+ """
625
+ if atoms:
626
+ if self.diversity:
627
+ ctrl, avoided = self.add_diversity(ctrl, seeds, avoided)
628
+ # exclude solution and its superset
629
+ ctrl.add("skip", [], f":- {','.join(map(str,atoms))}.")
630
+ ##################################################
631
+ # exclude only solution, keep superset
632
+ # not used because superset are so much that it founds less solutions
633
+ # than when we delete the superset (more networks work, more solution
634
+ # found per network)
635
+ # code kept in case it is needed
636
+
637
+ #ctrl.add("skip", [], f":- {','.join(map(str,atoms))}, #count{{M: seed(M,_)}} = {len(atoms)}.")
638
+ ##################################################
639
+ ctrl.ground([("skip",[])])
640
+ return ctrl, avoided
641
+
642
+
643
+ def get_gc_mode(self):
644
+ mode = 'REASONING GUESS-CHECK'
645
+ if self.diversity:
646
+ mode = 'REASONING GUESS-CHECK DIVERSITY'
647
+ return mode
648
+
649
+
650
+ def guess_check(self, queue:Queue, full_option:list, asp_files:list, search_mode:str, full_path:str, is_one_model:bool=False):
651
+ """Guess and Check mode. Find a solution with Clingo package, check if the solution has flux on objective reaction.
652
+ This function works with multiprocessing in order to manage time limit.
653
+ Interacts with ASP solver and exclude supersets of the current tested solution.
654
+ If diversity is asked, the function add_diversity is called.
655
+
656
+ Args:
657
+ queue (Queue): Queue for multiprocessing program (managing time limit)
658
+ full_option (list): All Clingo option
659
+ asp_files (list): List of needed ASP files to solve ASP (Clingo package)
660
+ search_mode (str): Optimization selected for the search (submin/minmize and enumeration/optimum)
661
+ full_path (str): Full path for temp file needed to get back solution when time out
662
+ is_one_model (bool, optional): Define if the solution we want is to fin the optimum when minimize is used (before enumration).
663
+ Defaults to False.
664
+ """
665
+ solution_list = dict()
666
+ avoided = []
667
+ all_time_solve = 0
668
+ all_time_ground = 0
669
+
670
+ # No limit on number of solution
671
+ no_limit_solution = False
672
+ if self.number_solution == 0:
673
+ no_limit_solution = True
674
+
675
+ full_option_seeds = self.complete_option(full_option,0)
676
+
677
+ ctrl = self.control_init(full_option_seeds, asp_files, True)
678
+ solution_idx = 1
679
+ number_rejected = 0
680
+ start_time = time()
681
+ while ((len(solution_list) < self.number_solution \
682
+ and not is_one_model and not no_limit_solution) \
683
+ or is_one_model or no_limit_solution )\
684
+ and \
685
+ (self.time_limit and float(all_time_ground + all_time_solve) < float(self.time_limit)
686
+ or not self.time_limit):
687
+ with ctrl.solve(yield_=True) as h:
688
+ seeds = list()
689
+ transferred = list()
690
+ for model in h:
691
+ atoms = model.symbols(shown=True)
692
+
693
+ seeds, seeds_full, transferred, _ = self.get_seeds_transfers(atoms)
694
+ size = len(seeds)
695
+
696
+ transf_short, trans_complete, trans_solution_list =self.get_transfers_info(transferred)
697
+
698
+ # is_one_model is for minimize.
699
+ # Community doesn't have minimize
700
+ if not is_one_model:
701
+ break
702
+ else:
703
+ self.optimum=model.cost
704
+ if not seeds:
705
+ if is_one_model:
706
+ name = 'model_one_solution'
707
+ solution_list[name] = ["size", 0] + \
708
+ ["Set of seeds", []]
709
+ self.optimum_found = True
710
+ else:
711
+ if transferred:
712
+ transf_short, trans_complete, trans_solution_list = self.get_transfers_info(transferred)
713
+ else:
714
+ transf_short = trans_complete = ""
715
+ trans_solution_list = list()
716
+ break
717
+ # Sum all grounding time together and all solving time together
718
+ stats = ctrl.statistics
719
+ total_time = stats["summary"]["times"]["total"]
720
+ time_solve = stats["summary"]["times"]["solve"]
721
+ time_ground = total_time - time_solve
722
+
723
+ all_time_solve += float(time_solve)
724
+ all_time_ground += float(time_ground)
725
+ res = self.network.check_seeds(seeds, trans_solution_list)
726
+ if res[0]:
727
+ logger.print_log(f'CHECK Solution {size} seeds -> OK\n', 'debug')
728
+ # valid solution
729
+ if not is_one_model:
730
+ message = color.cyan_light + f"Answer: {solution_idx}{color.reset} ({size} seeds{transf_short})\n"
731
+ self.print_answer(message, seeds, seeds_full, trans_complete)
732
+ name = 'model_'+str(solution_idx)
733
+
734
+ solution_list, solution_temp = self.complete_solutions(solution_list, name, size, seeds,
735
+ trans_solution_list, res[1], number_rejected)
736
+ save(full_path, "", solution_temp, "tsv", True)
737
+ # exclude solutions and its supersets
738
+ ctrl, avoided= self.guess_check_constraints(ctrl, atoms, seeds, avoided)
739
+ mode = self.get_gc_mode()
740
+ self.network.add_result_seeds(mode, search_mode, name, size, seeds, flux_cobra=res[1], transferred_list=trans_solution_list)
741
+ solution_idx +=1
742
+ else:
743
+ name = 'model_one_solution'
744
+ solution_list, solution_temp = self.complete_solutions(solution_list, name, size, seeds,
745
+ trans_solution_list, res[1], number_rejected)
746
+ logger.print_log(f"Optimum found.", "info")
747
+ self.optimum_found = True
748
+ mode = 'REASONING GUESS-CHECK'
749
+ # Do not exclude superset because we will rerun the minimize by set the size
750
+ # and we want to find back this first minimize found
751
+ if self.diversity:
752
+ ctrl, avoided = self.add_diversity(ctrl, seeds, avoided)
753
+ mode = 'REASONING GUESS-CHECK DIVERSITY'
754
+ save(full_path, self.temp_dir, solution_temp, "tsv", True)
755
+
756
+ self.network.add_result_seeds(mode, search_mode, name, size, seeds, flux_cobra=res[1], transferred_list=trans_solution_list)
757
+ self.get_separate_optimum()
758
+ if self.network.is_subseed:
759
+ logger.print_log(f"Number of producible targets: {- self.opt_prod_tgt}", "info")
760
+ logger.print_log(f"Minimal size of seed set is {self.opt_size}\n", "info")
761
+ break
762
+
763
+ else:
764
+ logger.print_log(f'CHECK Solution {size} seeds -> KO\n', 'debug')
765
+ #logger.print_log(f'{seeds}\n', 'debug')
766
+
767
+ ctrl, avoided= self.guess_check_constraints(ctrl, atoms, seeds, avoided)
768
+ mode = self.get_gc_mode()
769
+ number_rejected +=1
770
+
771
+ current_timer = time() - start_time
772
+ # write all 100 rejected
773
+ # or write from 5 minute before finishing the process near to finish the process
774
+ if number_rejected%100 == 0 \
775
+ or (current_timer!=0 and current_timer > self.time_limit_minute*60 - 300):
776
+ self.temp_rejected(number_rejected, full_path)
777
+
778
+ # Needed when all solutions are rejected and not 100 solution tested
779
+ # to retrieve the last number rejected and save into temp file
780
+ if not existing_file(full_path):
781
+ solution_temp = [None, None, None, number_rejected, None, None]
782
+
783
+ if not is_one_model:
784
+ save(full_path, "", solution_temp, "tsv", True)
785
+ logger.print_log(f'Rejected solution during process: {number_rejected} \n', 'info')
786
+
787
+ ctrl.cleanup()
788
+ ctrl.interrupt()
789
+ queue.put([self, solution_list, all_time_ground, all_time_solve, number_rejected])
790
+
791
+ ########################################################