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