seed2lp 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. seed2lp/__init__.py +12 -0
  2. seed2lp/__main__.py +837 -0
  3. seed2lp/_version.py +2 -0
  4. seed2lp/argument.py +717 -0
  5. seed2lp/asp/atom_for_transfers.lp +7 -0
  6. seed2lp/asp/community_heuristic.lp +3 -0
  7. seed2lp/asp/community_search.lp +14 -0
  8. seed2lp/asp/constraints_targets.lp +15 -0
  9. seed2lp/asp/definition_atoms.lp +87 -0
  10. seed2lp/asp/enum-cc.lp +50 -0
  11. seed2lp/asp/flux.lp +70 -0
  12. seed2lp/asp/limit_transfers.lp +9 -0
  13. seed2lp/asp/maximize_flux.lp +2 -0
  14. seed2lp/asp/maximize_produced_target.lp +7 -0
  15. seed2lp/asp/minimize.lp +8 -0
  16. seed2lp/asp/seed-solving.lp +116 -0
  17. seed2lp/asp/seed_external.lp +1 -0
  18. seed2lp/asp/show_seeds.lp +2 -0
  19. seed2lp/asp/show_tranfers.lp +1 -0
  20. seed2lp/asp/test.lp +61 -0
  21. seed2lp/clingo_lpx.py +236 -0
  22. seed2lp/color.py +34 -0
  23. seed2lp/config.yaml +56 -0
  24. seed2lp/description.py +424 -0
  25. seed2lp/file.py +151 -0
  26. seed2lp/flux.py +365 -0
  27. seed2lp/linear.py +431 -0
  28. seed2lp/log_conf.yaml +25 -0
  29. seed2lp/logger.py +112 -0
  30. seed2lp/metabolite.py +46 -0
  31. seed2lp/network.py +1921 -0
  32. seed2lp/reaction.py +207 -0
  33. seed2lp/reasoning.py +459 -0
  34. seed2lp/reasoningcom.py +753 -0
  35. seed2lp/reasoninghybrid.py +791 -0
  36. seed2lp/resmod.py +74 -0
  37. seed2lp/sbml.py +307 -0
  38. seed2lp/scope.py +124 -0
  39. seed2lp/solver.py +333 -0
  40. seed2lp/temp_flux_com.py +74 -0
  41. seed2lp/utils.py +237 -0
  42. seed2lp-2.0.0.dist-info/METADATA +404 -0
  43. seed2lp-2.0.0.dist-info/RECORD +53 -0
  44. seed2lp-2.0.0.dist-info/WHEEL +5 -0
  45. seed2lp-2.0.0.dist-info/entry_points.txt +2 -0
  46. seed2lp-2.0.0.dist-info/licenses/LICENCE.txt +145 -0
  47. seed2lp-2.0.0.dist-info/top_level.txt +2 -0
  48. tests/__init__.py +0 -0
  49. tests/fba.py +147 -0
  50. tests/full_network.py +166 -0
  51. tests/normalization.py +188 -0
  52. tests/target.py +286 -0
  53. tests/utils.py +181 -0
seed2lp/solver.py ADDED
@@ -0,0 +1,333 @@
1
+ # Object Solver constitued of
2
+ # - run_mode (str): Running command used (full or target)
3
+ # - network (Network): Object Network
4
+ # - time_limit_minute (float): Time limit given by user in minutes
5
+ # - time_limit (int): Time limit converted in seconds for resolutions
6
+ # - number_solution (int): Limit number of solutions to find
7
+ # - clingo_configuration (str): Configuration for clingo resolution (jumpy or not, or other)
8
+ # - clingo_strategy (str): Strategy for clingo resolution (usc,oll or not, other)
9
+ # - enumeration (bool): Enumerate the solutions limited by the number of solutions
10
+ # - intersection (bool): Find the intersection of all solutions without limitation (give one solution)
11
+ # - union (bool): Find the union of all solutions without limitation (give one solution)
12
+ # - minimize (bool): Search the minimal carinality of solutions
13
+ # - subset_minimal (bool): Search the subset minimal solutions
14
+ # - clingo_constant (str): Set the value of constant in lp file for search
15
+ # - one_model_unsat (bool): Set if the minimze solution is unsatisfiable
16
+ # - optimum (int): Value of minimal cardinality if not unsatisfiable
17
+ # - output (dict): List of all solutions
18
+ # - timer_list (dict): List of all timers to find solution
19
+ # - verbose (bool): Set debug mode
20
+
21
+ from os import path
22
+ from .network import Network
23
+ from .file import write_instance_file
24
+ from dataclasses import dataclass
25
+ from . import logger
26
+ from . import color
27
+
28
+
29
+
30
+ @dataclass
31
+ class ASP_CLINGO:
32
+ SRC_DIR = path.dirname(path.abspath(__file__))
33
+ ASP_SRC_SEED_SOLVING = path.join(SRC_DIR, 'asp/seed-solving.lp')
34
+ ASP_SRC_CONSTRAINTS_TARGET = path.join(SRC_DIR, 'asp/constraints_targets.lp')
35
+ ASP_SRC_MINIMIZE = path.join(SRC_DIR, 'asp/minimize.lp')
36
+ ASP_SRC_FLUX = path.join(SRC_DIR, 'asp/flux.lp')
37
+ ASP_SRC_MAXIMIZE_FLUX = path.join(SRC_DIR, 'asp/maximize_flux.lp')
38
+ ASP_SRC_MAXIMIZE_PRODUCED_TARGET = path.join(SRC_DIR, 'asp/maximize_produced_target.lp')
39
+ ASP_SRC_COMMUNITY = path.join(SRC_DIR, 'asp/community_search.lp')
40
+ ASP_SRC_SHOW_SEEDS = path.join(SRC_DIR, 'asp/show_seeds.lp')
41
+ ASP_SRC_SHOW_TRANSFERS = path.join(SRC_DIR, 'asp/show_tranfers.lp')
42
+ ASP_SRC_LIMIT_TRANSFERS = path.join(SRC_DIR, 'asp/limit_transfers.lp')
43
+ ASP_SRC_ATOM_TRANSF = path.join(SRC_DIR, 'asp/atom_for_transfers.lp')
44
+ ASP_SRC_ATOMS = path.join(SRC_DIR, 'asp/definition_atoms.lp')
45
+ ASP_SRC_COM_HEURISTIC = path.join(SRC_DIR, 'asp/community_heuristic.lp')
46
+ ASP_SRC_SEED_EXTERNAL = path.join(SRC_DIR, 'asp/seed_external.lp')
47
+ CLINGO_CONFIGURATION = {
48
+ 'minimize-enumeration': ['--project=show', '--opt-mode=enum'],
49
+ 'minimize-union': ['--enum-mode=brave', '--opt-mode=enum'],
50
+ 'minimize-intersection': ['--enum-mode=cautious', '--opt-mode=enum'],
51
+ 'minimize-one-model': None,
52
+ 'submin-enumeration': ['--heuristic=Domain', '--enum-mode=domRec', '--dom-mod=5,16'], # modifier 5: false / pick 16 : Atoms that are shown
53
+ 'submin-intersection': ['--heuristic=Domain', '--enum-mode=cautious', '--dom-mod=5,16'], # modifier 5: false / pick 16 : Atoms that are shown
54
+ }
55
+ ASW_FLAG, OPT_FLAG, OPT_FOUND = 'Answer: ', 'Optimization: ', 'OPTIMUM FOUND'
56
+
57
+ @dataclass
58
+ class GROUNDING:
59
+ #TODO: CLEAN GROUND MODE IF NOT USED
60
+ GROUND = False
61
+
62
+ ###################################################################
63
+ ########################## Class Solver ##########################
64
+ ###################################################################
65
+ class Solver:
66
+ def __init__(self, run_mode:str, network:Network,
67
+ time_limit_minute:float=None, number_solution:int=None,
68
+ clingo_configuration:str=None, clingo_strategy:str=None,
69
+ intersection:bool=False, union:bool=False,
70
+ minimize:bool=False, subset_minimal:bool=False,
71
+ temp_dir:str=None, short_option:str=None, run_solve:str=None,
72
+ verbose:bool=False, community_mode:str=None):
73
+ """"Initialize Object Solver
74
+
75
+ Args:
76
+ run_mode (str): Running command used (full or target)
77
+ network (Network): Network constructed
78
+ time_limit_minute (float, optional): Time limit given by user in minutes . Defaults to None.
79
+ number_solution (int, optional): Limit number of solutions to find. Defaults to 100. if -1, no enumeration
80
+ clingo_configuration (str, optional): Configuration for clingo resolution . Defaults to None.
81
+ clingo_strategy (str, optional): Strategy for clingo resolution. Defaults to None.
82
+ intersection (bool, optional): Find the intersection of all solutions without limitation (give one solution). Defaults to False.
83
+ union (bool, optional): Find the union of all solutions without limitation (give one solution). Defaults to False.
84
+ minimize (bool, optional): Search the minimal carinality of solutions. Defaults to False.
85
+ subset_minimal (bool, optional): Search the subset minimal solutions. Defaults to False.
86
+ temp_dir (str, optional): Temporary directory for saving instance file and clingo outputs. Defaults to None.
87
+ short_option (str, optional): Short way to write option on filename. Defaults to None.
88
+ run_solve (str, optional): Solving run used (reasoning, filter, guess-check, guess-check-div)
89
+ verbose (bool, optional): Set debug mode. Defaults to False.
90
+ """
91
+
92
+ self.is_linear:bool
93
+ self.asp = ASP_CLINGO()
94
+ self.ground = GROUNDING().GROUND
95
+ self.run_mode = run_mode
96
+ self.network = network
97
+ self.time_limit_minute = time_limit_minute
98
+ self.set_time_limit()
99
+ self.number_solution = number_solution
100
+ self._set_clingo_configuration(clingo_configuration)
101
+ self._set_clingo_strategy(clingo_strategy)
102
+ self.enumeration = True
103
+ if(number_solution == -1):
104
+ self.enumeration = False
105
+ self.intersection = intersection
106
+ self.union = union
107
+ self.minimize = minimize
108
+ self.subset_minimal = subset_minimal
109
+ self.clingo_constant = list()
110
+ self.one_model_unsat = True
111
+ self.optimum = tuple()
112
+ self.optimum_found = False
113
+ self.opt_prod_tgt = None
114
+ self.opt_size = None
115
+ self.output = dict()
116
+ self.timer_list = dict()
117
+ self.verbose = verbose
118
+ self.messages = list()
119
+ self.temp_dir = temp_dir
120
+ self.short_option = short_option
121
+ self.run_solve = run_solve
122
+ if self.run_solve == "guess_check_div":
123
+ self.diversity = True
124
+ else:
125
+ self.diversity = False
126
+ self.grounded = str()
127
+ self.temp_result_file = str()
128
+ self.community_mode = community_mode
129
+
130
+ self.asp_files = [self.asp.ASP_SRC_SEED_SOLVING, self.asp.ASP_SRC_CONSTRAINTS_TARGET, self.asp.ASP_SRC_ATOMS]
131
+ self._set_instance_file()
132
+
133
+
134
+ ######################## SETTER ########################
135
+ def set_time_limit(self):
136
+ """Convert time limit minute into seconds for resolutions
137
+ """
138
+ if self.time_limit_minute != 0:
139
+ self.time_limit=self.time_limit_minute*60
140
+ else:
141
+ self.time_limit=None
142
+
143
+
144
+ def _set_clingo_configuration(self, clingo_configuration:str):
145
+ """Prepare configuration command option for resolution
146
+
147
+ Args:
148
+ clingo_configuration (str): configuration mode
149
+ """
150
+ if clingo_configuration != "none":
151
+ self.clingo_configuration = f"--configuration={clingo_configuration}"
152
+ else:
153
+ self.clingo_configuration = ""
154
+
155
+ def _set_clingo_strategy(self, clingo_strategy:str):
156
+ """Prepare strategy command option for resolution
157
+
158
+ Args:
159
+ clingo_strategy (str): strategy mode
160
+ """
161
+ if clingo_strategy != "none":
162
+ self.clingo_strategy = f"--opt-strategy={clingo_strategy}"
163
+ else:
164
+ self.clingo_strategy = ""
165
+
166
+ def _set_instance_file(self):
167
+ """Prepare ASP instance filename for saving into temporary directory file
168
+ """
169
+ filename = f'instance_{self.network.name}_{self.short_option}'
170
+ self.network.instance_file = path.join(self.temp_dir,f'{filename}.lp')
171
+ write_instance_file(self.network.instance_file, self.network.facts)
172
+ self.asp_files.append(self.network.instance_file)
173
+ logger.log.info(f"Instance file written: {self.network.instance_file}")
174
+
175
+
176
+ def _set_temp_result_file(self):
177
+ self.temp_result_file = f'temp_{self.network.name}_{self.short_option}'
178
+
179
+ ########################################################
180
+
181
+ ######################## METHODS ########################
182
+
183
+
184
+ def get_solutions_infos(self, search_mode:str=""):
185
+ """Get infos of solving mode for messsages and outputs.
186
+
187
+ Args:
188
+ search_mode (str, optional): Describe the mode of resolution. Defaults to "".
189
+
190
+ Returns:
191
+ str, str, str: mode_message, model_type, output_type
192
+ """
193
+ mode_message = ""
194
+ model_type = ""
195
+ output_type = ""
196
+ match search_mode:
197
+ case "minimize-one-model":
198
+ mode_message="Minimize optimum"
199
+ model_type="model_one_solution"
200
+ output_type = 'MINIMIZE OPTIMUM'
201
+ case "minimize-intersection":
202
+ mode_message="Minimize intersection"
203
+ model_type="model_intersection"
204
+ output_type = 'MINIMIZE INTERSECTION'
205
+ case "minimize-union":
206
+ mode_message="Minimize union"
207
+ model_type="model_union"
208
+ output_type = 'MINIMIZE UNION'
209
+ case "minimize-enumeration":
210
+ output_type = 'MINIMIZE ENUMERATION'
211
+ case "submin-enumeration":
212
+ output_type = 'SUBSET MINIMAL ENUMERATION'
213
+ case "submin-intersection":
214
+ mode_message="Subset Minimal intersection"
215
+ model_type="model_intersection"
216
+ output_type = 'SUBSET MINIMAL INTERSECTION'
217
+
218
+
219
+ full_option=[self.clingo_configuration, self.clingo_strategy]
220
+ greedy_clingo_option=self.asp.CLINGO_CONFIGURATION[search_mode]
221
+ if greedy_clingo_option:
222
+ full_option = [*full_option, *greedy_clingo_option]
223
+ full_option = list(filter(None, full_option))
224
+ return full_option, mode_message, model_type, output_type
225
+
226
+
227
+ def init_const(self):
228
+ """ Inititate ASP constants
229
+ """
230
+ self.clingo_constant = ['-c']
231
+ match self.run_mode:
232
+ case 'target':
233
+ logger.print_log("Mode : TARGET", "info")
234
+ if not self.network.targets_as_seeds:
235
+ logger.print_log('Option: TARGETS ARE FORBIDDEN SEEDS', "info")
236
+ logger.print_log(f'Search seeds validating the {len(self.network.targets)} targets…','debug')
237
+ self.clingo_constant.append('run_mode=target')
238
+ case 'full':
239
+ logger.print_log("Mode : FULL NETWORK", "info")
240
+ logger.print_log("Search seeds validating all metabolites as targets…",'debug')
241
+ self.clingo_constant.append('run_mode=full')
242
+ case 'fba':
243
+ logger.print_log("Mode : FBA", "info")
244
+ if not self.network.targets_as_seeds:
245
+ logger.print_log('Option: TARGETS ARE FORBIDDEN SEEDS', "info")
246
+ logger.print_log('Info: Targets are reactant of objective', "info")
247
+ logger.print_log("Search seeds aleatory…",'debug')
248
+ self.clingo_constant.append('run_mode=fba')
249
+ case 'community':
250
+ match self.community_mode:
251
+ case "global":
252
+ logger.print_log("Community Mode : Global Subset Minimal", "info")
253
+ case "bisteps":
254
+ logger.print_log("Community Mode : Bisteps Subset Minimal", "info")
255
+ case "delsupset":
256
+ logger.print_log("Community Mode : Delete superset of seeds", "info")
257
+ if not self.network.targets_as_seeds:
258
+ logger.print_log('Option: TARGETS ARE FORBIDDEN SEEDS', "info")
259
+ logger.print_log(f'Search seeds validating the {len(self.network.targets)} targets…','debug')
260
+ self.clingo_constant.append('run_mode=target')
261
+
262
+ if self.network.accumulation:
263
+ logger.print_log('ACCUMULATION: Authorized', "info")
264
+ self.clingo_constant.append('-c')
265
+ self.clingo_constant.append('accu=1')
266
+ else:
267
+ logger.print_log('ACCUMULATION: Forbidden', "info")
268
+ self.clingo_constant.append('-c')
269
+ self.clingo_constant.append('accu=0')
270
+
271
+ def get_separate_optimum(self):
272
+ """This function separate the optimisations if possible:
273
+ Maximization of produced target (rank 2)
274
+ Minimization of set of seeds (rank 1)
275
+ When using multiple optimization with clingo, the solver gives back
276
+ a list of optimality found in order of importance in the asp files .
277
+ A higher rank means a higher importance.
278
+ """
279
+ if len(self.optimum)==1:
280
+ self.opt_prod_tgt = None
281
+ self.opt_size = self.optimum[0]
282
+ else:
283
+ self.opt_prod_tgt = self.optimum[0]
284
+ self.opt_size = self.optimum[1]
285
+
286
+ def get_message(self, mode:str=None):
287
+ """Get messages to put on terminal
288
+
289
+ Args:
290
+ mode (str, optional): witch kind of messages to chose. Defaults to None.
291
+ """
292
+ match mode:
293
+ case 'subsetmin':
294
+ logger.print_log("\n____________________________________________","info",color.purple)
295
+ logger.print_log("____________________________________________\n", "info",color.purple)
296
+ logger.print_log(f"Sub Mode: {color.bold}SUBSET MINIMAL{color.reset}".center(55), "info")
297
+ logger.print_log("____________________________________________", "info",color.purple)
298
+ logger.print_log("____________________________________________\n", "info",color.purple)
299
+ case "minimize":
300
+ logger.print_log("\n____________________________________________","info",color.purple)
301
+ logger.print_log("____________________________________________\n", "info",color.purple)
302
+ logger.print_log(f"Sub Mode: {color.bold}MINIMIZE{color.reset}".center(55), "info")
303
+ logger.print_log("____________________________________________", "info",color.purple)
304
+ logger.print_log("____________________________________________\n", "info",color.purple)
305
+ case "one solution":
306
+ logger.print_log(f"\n~~~~~~~~~~~~~~~ {color.bold}One solution{color.reset} ~~~~~~~~~~~~~~~", "info")
307
+ case "intersection":
308
+ logger.print_log(f"\n~~~~~~~~~~~~~~~ {color.bold}Intersection{color.reset} ~~~~~~~~~~~~~~~", "info")
309
+ case "enumeration":
310
+ logger.print_log(f"\n~~~~~~~~~~~~~~~~ {color.bold}Enumeration{color.reset} ~~~~~~~~~~~~~~~", "info")
311
+ case "union":
312
+ logger.print_log(f"\n~~~~~~~~~~~~~~~~~~~ {color.bold}Union{color.reset} ~~~~~~~~~~~~~~~~~~", "info")
313
+ case "end":
314
+ logger.print_log('############################################\n\n', "info", color.cyan_light)
315
+ case "optimum error":
316
+ logger.print_log("\n____________________________________________","info")
317
+ logger.print_log('ABORTED: No objective funcion found \
318
+ \nPlease correct the SBML file to contain either \
319
+ \n - a function with "BIOMASS" (not case sensiive) in the name \
320
+ \n - a function in the objective list', "error")
321
+ case "command":
322
+ logger.print_log(" Command", "debug")
323
+ case "classic":
324
+ logger.print_log(f"\n················ {color.bold}Classic mode{color.reset} ···············", "info")
325
+ case "filter":
326
+ logger.print_log(f"\n················ {color.bold}Filter mode{color.reset} ···············", "info")
327
+ case "guess_check":
328
+ logger.print_log(f"\n·············· {color.bold}Guess-Check mode{color.reset} ············", "info")
329
+ case "guess_check_div":
330
+ logger.print_log(f"\n····· {color.bold}Guess-Check with diversity mode{color.reset} ······", "info")
331
+
332
+
333
+
@@ -0,0 +1,74 @@
1
+ import cobra
2
+ model=cobra.io.read_sbml_model("networks/toys_communities/communities/comex.sbml")
3
+ model=cobra.io.read_sbml_model("networks/toys_communities/sbml/B1.sbml")
4
+
5
+ for elem in model.boundary:
6
+ if not elem.reactants and elem.upper_bound > 0:
7
+ if elem.lower_bound > 0:
8
+ elem.lower_bound = 0
9
+ elem.upper_bound = 0.0
10
+ if not elem.products and elem.lower_bound < 0:
11
+ if elem.upper_bound < 0:
12
+ elem.upper_bound = 0
13
+ elem.lower_bound = 0.0
14
+
15
+ model.reactions.EX_A.upper_bound = 0
16
+ model.reactions.EX_B.upper_bound = 0
17
+ model.reactions.EX_C.upper_bound = 0
18
+ model.reactions.EX_E.upper_bound = 0
19
+ #model.reactions.EX_A.lower_bound = -1000
20
+ model.reactions.B1_EX_A.lower_bound = -1000
21
+ model.reactions.B2_EX_A.lower_bound = -1000
22
+ model.reactions.B2_EX_B.lower_bound = -1000
23
+ #model.reactions.EX_B.lower_bound = -1000
24
+ model.reactions.EX_C.lower_bound = 0
25
+ model.reactions.EX_E.lower_bound = 0
26
+
27
+
28
+ same_flux = model.problem.Constraint(
29
+ model.reactions.B1_Biom1.flux_expression - model.reactions.B2_Biom2.flux_expression,
30
+ lb=0, ub=0)
31
+ model.add_cons_vars(same_flux)
32
+
33
+
34
+ #biom_com = cobra.Reaction("Biom_com")
35
+ #biom_com.name = "Biom_com"
36
+ #biom_com.lower_bound = 0
37
+ #biom_com.upper_bound = 1000
38
+ #biom_com.add_metabolites({
39
+ # model.metabolites.get_by_id("BM1_e"): -1.0,
40
+ # model.metabolites.get_by_id("BM2_e"): -1.0})
41
+ #
42
+ #model.reactions.add(biom_com)
43
+
44
+
45
+ model.objective = "B1_Biom1"
46
+ solution = model.optimize()
47
+ print(solution.fluxes['B1_Biom1'], solution.fluxes['B2_Biom2'], solution.objective_value)
48
+
49
+ for r in model.reactions:
50
+ print(f"{r} -- {r.lower_bound} -- {r.upper_bound}")
51
+
52
+ #######################################################################################################################################################
53
+
54
+
55
+ import cobra
56
+ model=cobra.io.read_sbml_model("networks/toys_communities/communities/comex_2.sbml")
57
+
58
+ for elem in model.boundary:
59
+ if not elem.reactants and elem.upper_bound > 0:
60
+ if elem.lower_bound > 0:
61
+ elem.lower_bound = 0
62
+ elem.upper_bound = 0.0
63
+ if not elem.products and elem.lower_bound < 0:
64
+ if elem.upper_bound < 0:
65
+ elem.upper_bound = 0
66
+ elem.lower_bound = 0.0
67
+
68
+ model.reactions.EX_A.upper_bound = 0
69
+ model.reactions.EX_B.upper_bound = 0
70
+ model.reactions.EX_A.lower_bound = -1000
71
+ model.reactions.EX_B.lower_bound = 0
72
+
73
+ #same_flux = model.problem.Constraint(model.reactions.Biom1_B1.flux_expression - model.reactions.Biom2_B2.flux_expression, lb=0, ub=0)
74
+ #model.add_cons_vars(same_flux)
seed2lp/utils.py ADDED
@@ -0,0 +1,237 @@
1
+ """Utilitaries"""
2
+ import os
3
+ import clyngor
4
+ import re
5
+ from re import findall
6
+ from . import logger
7
+ from csv import reader
8
+
9
+
10
+ def solve(*args, **kwargs):
11
+ "Wrapper around clyngor.solve"
12
+ kwargs.setdefault('use_clingo_module', False)
13
+ try:
14
+ return clyngor.solve(*args, **kwargs)
15
+ except FileNotFoundError as err:
16
+ if 'clingo' in err.filename:
17
+ logger.log.error('Binary file clingo is not accessible in the PATH.')
18
+ exit(1)
19
+ else: raise err
20
+
21
+
22
+ def get_ids_from_file(fname:str, asp_atome_type:str=None) -> [str]:
23
+ """Get metabolites id from seeds file, forbidden seeds file or possible seeds file
24
+
25
+ Args:
26
+ fname (str): file path
27
+ asp_atome_type (str, optional): Type of atome for facts. Defaults to None.
28
+
29
+ Raises:
30
+ NotImplementedError: Target file of extension [ext] not implemented
31
+
32
+ Returns:
33
+ [str]: List of metabolite
34
+ """
35
+ "Yield identifiers of seeds/targets/metabolites found in given sbml or text or lp file"
36
+ metabolit_list = list()
37
+ ext = os.path.splitext(fname)[1]
38
+ #if ext in {'.sbml', '.xml'}: # sbml data
39
+ # from .sbml import read_SBML_species
40
+ # yield from read_SBML_species(fname)
41
+ if ext in {'.lp'}: # ASP data
42
+ for model in solve(fname).by_arity:
43
+ for line, in model.get(f'{asp_atome_type}/1', ()):
44
+ line = unquoted(line)
45
+ if re.search("^M_*",line):
46
+ metabolit_list.append(line)
47
+ else:
48
+ metabolit_list.append(f'M_{line}')
49
+ elif ext in {'.txt', ''}: # file, one line per metabolite
50
+ with open(fname) as fd:
51
+ for line in map(str.strip, fd):
52
+ if line:
53
+ if re.search("^M_*",line):
54
+ metabolit_list.append(line)
55
+ else:
56
+ metabolit_list.append(f'M_{line}')
57
+ else:
58
+ raise NotImplementedError(f"Target file of ext {ext}: {fname}")
59
+ return metabolit_list
60
+
61
+
62
+ def get_targets_from_file(fname:str, is_community:bool):
63
+ """Get metabolites id or reactions id from target file
64
+
65
+ Args:
66
+ fname (str): Target file path
67
+ is_community (bool): Community mode
68
+
69
+ Raises:
70
+ ValueError: The element [element] misses prefix M_ or R_"
71
+ NotImplementedError: The [file name] extension has to be ".txt".
72
+ ValueError: Multiple objective reaction found
73
+
74
+ Returns:
75
+ [str],[str]: List of target and list of objective reaction
76
+ """
77
+ target_list=dict()
78
+ objective_reaction_list = list()
79
+ ext = os.path.splitext(fname)[1]
80
+ if ext in {'.txt', ".csv",''}: # file, one line per metabolite
81
+ # file = open(fname)
82
+ # tgts = reader(file, delimiter='\t')
83
+ with open(fname, "r") as f:
84
+ for line in f:
85
+ line = line.strip()
86
+ if not line:
87
+ continue # ignore empty line
88
+ data = re.split(r"[ \t]+", line)
89
+
90
+ obj_id=data[0]
91
+ if is_community:
92
+ if len(data)==2:
93
+ species=data[1]
94
+ else:
95
+ raise ValueError(f"invalid data, needs 2 elements (metabolite or reaction / species):\n {line}")
96
+ else:
97
+ if len(data)==1:
98
+ species=""
99
+ else:
100
+ raise ValueError(f"invalid data, needs 1 element (metabolite or reaction):\n {line}")
101
+
102
+ if re.search("^M_*",line):
103
+ prefixed_id=prefix_id_network(is_community, obj_id, species, "metabolite")
104
+ if obj_id in target_list:
105
+ target_list[obj_id].append(prefixed_id)
106
+ else:
107
+ target_list[obj_id]=[prefixed_id]
108
+ #target_list.append(line)
109
+ elif re.search("^R_*",line):
110
+ objective_reaction_list.append([species, obj_id])
111
+ else:
112
+ raise ValueError(f"\n{fname} : The element {line} misses prefix M_ or R_")
113
+ else:
114
+ raise NotImplementedError(f'\nThe {fname} extension has to be ".txt". Given: {ext}')
115
+
116
+ if len(objective_reaction_list) >1 and not is_community:
117
+ raise ValueError(f"\nMultiple objective reaction found in {fname}\n")
118
+ elif is_community:
119
+ no_duplicates = len({x[0] for x in objective_reaction_list}) == len(objective_reaction_list)
120
+ if not no_duplicates:
121
+ raise ValueError(f"\nMultiple objective reaction found in {fname} for same species\n")
122
+
123
+
124
+
125
+ return target_list, objective_reaction_list
126
+
127
+
128
+ def quoted(string:str) -> str:
129
+ r"""Return string, double quoted
130
+
131
+ >>> quoted('"a').replace('\\', '$')
132
+ '"$"a"'
133
+ >>> quoted('"a b"').replace('\\', '$')
134
+ '"a b"'
135
+ >>> quoted('a b').replace('\\', '$')
136
+ '"a b"'
137
+ >>> quoted('a\\"').replace('\\', '$')
138
+ '"a$""'
139
+ >>> quoted('a"').replace('\\', '$')
140
+ '"a$""'
141
+ >>> quoted('\\"a"').replace('\\', '$')
142
+ '"$"a$""'
143
+ >>> quoted('"').replace('\\', '$')
144
+ '"$""'
145
+
146
+ """
147
+ if len(string) > 1 and string[0] == '"' and string[-2] != '\\' and string[-1] == '"':
148
+ return string
149
+ else:
150
+ return '"' + string.replace('\\"', '"').replace('"', '\\"') + '"'
151
+
152
+
153
+ def unquoted(string:str) -> str:
154
+ r"""Remove surrounding double quotes if they are acting as such
155
+
156
+ >>> unquoted('"a').replace('\\', '$')
157
+ '$"a'
158
+ >>> unquoted('"a b"')
159
+ 'a b'
160
+ >>> unquoted('b"').replace('\\', '$')
161
+ 'b$"'
162
+ >>> unquoted('"b\\"').replace('\\', '$')
163
+ '$"b$"'
164
+
165
+ """
166
+ if string[0] == '"' and string[-2] != '\\' and string[-1] == '"':
167
+ return string[1:-1]
168
+ else:
169
+ return string.replace('\\"', '"').replace('"', '\\"')
170
+
171
+
172
+ def quoted_data(asp:str) -> str:
173
+ "Return the same atoms as found in given asp code, but with all arguments quoted"
174
+ def gen():
175
+ for model in clyngor.solve(inline=asp):
176
+ for pred, args in model:
177
+ yield f'{pred}(' + ','.join(quoted(str(arg)) for arg in args) + ').'
178
+ return ' '.join(gen())
179
+
180
+
181
+ def repair_json(json_str:str, is_clingo_lpx:bool=False):
182
+ """Function to add closing ] or } to the json after the process has been killed
183
+ delete also the last element of the json which can be not finished
184
+
185
+ Args:
186
+ proc_output (str): process output
187
+
188
+ Returns:
189
+ str: complete output on json format
190
+ """
191
+ close = {'{': '}',
192
+ '[': ']'}
193
+ if is_clingo_lpx:
194
+ output = json_str.rsplit('{', 1)[0]
195
+ output = output.rsplit(',', 1)[0]
196
+ else:
197
+ output = json_str.rsplit('\"model', 1)[0]
198
+ # get the list of caracter "{" "[" "]" "}" in the order of apparition
199
+ list_open_close=findall("{|\[|\]|}", output)
200
+ missing_list=list()
201
+ for car in list_open_close:
202
+ size=len(missing_list)
203
+ # delete the opening element when the closing element appear right after
204
+ if size!= 0 and ((missing_list[size -1] == "{" and car == "}")
205
+ or (missing_list[size -1] == "[" and car == "]")):
206
+ missing_list.pop(size -1)
207
+ else:
208
+ missing_list.append(car)
209
+ close_str=""
210
+ for i, open in reversed(list(enumerate(missing_list))):
211
+ close_str += "\n" + i * "\t" + close[open]
212
+ logger.log.warning("Output not totally recovered. Json has been repaired but might miss results")
213
+ return output+close_str
214
+
215
+ def prefix_id_network(is_community:bool, name:str, species:str="", type_element:str=""):
216
+ """Prefix Reaction or Metbolite by the network name (filename) if the tool is used for community.
217
+ For single network, nothing is prefixed.
218
+
219
+ Args:
220
+ name (str): ID of the element
221
+ species (str, optional): Network name (from filename). Defaults to "".
222
+ type_element: (str, optional): "reaction" or "metabolite" or no type. Defaults to "".
223
+
224
+ Returns:
225
+ str: The name prfixed by the network if needed
226
+ """
227
+ match is_community, type_element:
228
+ case True,"reaction":
229
+ return re.sub("^R_", f"R_{species}_",name)
230
+ case True,"metabolite":
231
+ return re.sub("^M_", f"M_{species}_",name)
232
+ case True,"metaid":
233
+ return re.sub("^meta_R_", f"meta_R_{species}_",name)
234
+ case True,_:
235
+ return f"{species}_{name}"
236
+ case _,_:
237
+ return name