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.
- seed2lp/__init__.py +12 -0
- seed2lp/__main__.py +837 -0
- seed2lp/_version.py +2 -0
- seed2lp/argument.py +717 -0
- seed2lp/asp/atom_for_transfers.lp +7 -0
- seed2lp/asp/community_heuristic.lp +3 -0
- seed2lp/asp/community_search.lp +14 -0
- seed2lp/asp/constraints_targets.lp +15 -0
- seed2lp/asp/definition_atoms.lp +87 -0
- seed2lp/asp/enum-cc.lp +50 -0
- seed2lp/asp/flux.lp +70 -0
- seed2lp/asp/limit_transfers.lp +9 -0
- seed2lp/asp/maximize_flux.lp +2 -0
- seed2lp/asp/maximize_produced_target.lp +7 -0
- seed2lp/asp/minimize.lp +8 -0
- seed2lp/asp/seed-solving.lp +116 -0
- seed2lp/asp/seed_external.lp +1 -0
- seed2lp/asp/show_seeds.lp +2 -0
- seed2lp/asp/show_tranfers.lp +1 -0
- seed2lp/asp/test.lp +61 -0
- seed2lp/clingo_lpx.py +236 -0
- seed2lp/color.py +34 -0
- seed2lp/config.yaml +56 -0
- seed2lp/description.py +424 -0
- seed2lp/file.py +151 -0
- seed2lp/flux.py +365 -0
- seed2lp/linear.py +431 -0
- seed2lp/log_conf.yaml +25 -0
- seed2lp/logger.py +112 -0
- seed2lp/metabolite.py +46 -0
- seed2lp/network.py +1921 -0
- seed2lp/reaction.py +207 -0
- seed2lp/reasoning.py +459 -0
- seed2lp/reasoningcom.py +753 -0
- seed2lp/reasoninghybrid.py +791 -0
- seed2lp/resmod.py +74 -0
- seed2lp/sbml.py +307 -0
- seed2lp/scope.py +124 -0
- seed2lp/solver.py +333 -0
- seed2lp/temp_flux_com.py +74 -0
- seed2lp/utils.py +237 -0
- seed2lp-2.0.0.dist-info/METADATA +404 -0
- seed2lp-2.0.0.dist-info/RECORD +53 -0
- seed2lp-2.0.0.dist-info/WHEEL +5 -0
- seed2lp-2.0.0.dist-info/entry_points.txt +2 -0
- seed2lp-2.0.0.dist-info/licenses/LICENCE.txt +145 -0
- seed2lp-2.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/fba.py +147 -0
- tests/full_network.py +166 -0
- tests/normalization.py +188 -0
- tests/target.py +286 -0
- tests/utils.py +181 -0
seed2lp/reasoningcom.py
ADDED
|
@@ -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
|
+
|