napistu 0.3.6__py3-none-any.whl → 0.4.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.
- napistu/__main__.py +28 -13
- napistu/consensus.py +19 -25
- napistu/constants.py +102 -83
- napistu/indices.py +3 -1
- napistu/ingestion/napistu_edgelist.py +4 -4
- napistu/ingestion/sbml.py +298 -295
- napistu/ingestion/string.py +14 -18
- napistu/ingestion/trrust.py +22 -27
- napistu/matching/interactions.py +41 -39
- napistu/matching/species.py +1 -1
- napistu/modify/gaps.py +2 -1
- napistu/network/constants.py +61 -45
- napistu/network/data_handling.py +1 -1
- napistu/network/neighborhoods.py +3 -3
- napistu/network/net_create.py +440 -616
- napistu/network/net_create_utils.py +734 -0
- napistu/network/net_propagation.py +1 -1
- napistu/network/{napistu_graph_core.py → ng_core.py} +57 -15
- napistu/network/ng_utils.py +28 -21
- napistu/network/paths.py +4 -4
- napistu/network/precompute.py +35 -74
- napistu/ontologies/genodexito.py +5 -1
- napistu/ontologies/renaming.py +4 -0
- napistu/sbml_dfs_core.py +127 -64
- napistu/sbml_dfs_utils.py +50 -0
- napistu/utils.py +132 -46
- {napistu-0.3.6.dist-info → napistu-0.4.0.dist-info}/METADATA +2 -2
- {napistu-0.3.6.dist-info → napistu-0.4.0.dist-info}/RECORD +47 -44
- tests/conftest.py +171 -13
- tests/test_consensus.py +74 -5
- tests/test_gaps.py +26 -15
- tests/test_network_data_handling.py +5 -2
- tests/test_network_net_create.py +93 -202
- tests/test_network_net_create_utils.py +538 -0
- tests/test_network_ng_core.py +19 -0
- tests/test_network_ng_utils.py +1 -1
- tests/test_network_precompute.py +5 -4
- tests/test_ontologies_renaming.py +28 -24
- tests/test_rpy2_callr.py +0 -1
- tests/test_rpy2_init.py +0 -1
- tests/test_sbml_dfs_core.py +165 -15
- tests/test_sbml_dfs_utils.py +45 -0
- tests/test_utils.py +45 -2
- {napistu-0.3.6.dist-info → napistu-0.4.0.dist-info}/WHEEL +0 -0
- {napistu-0.3.6.dist-info → napistu-0.4.0.dist-info}/entry_points.txt +0 -0
- {napistu-0.3.6.dist-info → napistu-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {napistu-0.3.6.dist-info → napistu-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,538 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
import pandas as pd
|
6
|
+
|
7
|
+
from napistu.network import net_create_utils
|
8
|
+
from napistu.constants import (
|
9
|
+
MINI_SBO_FROM_NAME,
|
10
|
+
SBML_DFS,
|
11
|
+
SBOTERM_NAMES,
|
12
|
+
VALID_SBO_TERM_NAMES,
|
13
|
+
)
|
14
|
+
from napistu.network.constants import (
|
15
|
+
DROP_REACTIONS_WHEN,
|
16
|
+
NAPISTU_GRAPH_EDGES,
|
17
|
+
NAPISTU_GRAPH_NODE_TYPES,
|
18
|
+
VALID_GRAPH_WIRING_APPROACHES,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
def test_format_interactors(reaction_species_examples):
|
23
|
+
|
24
|
+
r_id = "foo"
|
25
|
+
# interactions are formatted
|
26
|
+
graph_hierarchy_df = net_create_utils.create_graph_hierarchy_df("regulatory")
|
27
|
+
|
28
|
+
assert (
|
29
|
+
net_create_utils.format_tiered_reaction_species(
|
30
|
+
reaction_species_examples["valid_interactor"],
|
31
|
+
r_id,
|
32
|
+
graph_hierarchy_df,
|
33
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
34
|
+
).shape[0]
|
35
|
+
== 1
|
36
|
+
)
|
37
|
+
|
38
|
+
# simple reaction with just substrates and products
|
39
|
+
assert (
|
40
|
+
net_create_utils.format_tiered_reaction_species(
|
41
|
+
reaction_species_examples["sub_and_prod"],
|
42
|
+
r_id,
|
43
|
+
graph_hierarchy_df,
|
44
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
45
|
+
).shape[0]
|
46
|
+
== 2
|
47
|
+
)
|
48
|
+
|
49
|
+
# add a stimulator (activator)
|
50
|
+
rxn_edges = net_create_utils.format_tiered_reaction_species(
|
51
|
+
reaction_species_examples["stimulator"],
|
52
|
+
r_id,
|
53
|
+
graph_hierarchy_df,
|
54
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
55
|
+
)
|
56
|
+
|
57
|
+
assert rxn_edges.shape[0] == 3
|
58
|
+
assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "sub"]
|
59
|
+
|
60
|
+
# add catalyst + stimulator
|
61
|
+
rxn_edges = net_create_utils.format_tiered_reaction_species(
|
62
|
+
reaction_species_examples["all_entities"],
|
63
|
+
r_id,
|
64
|
+
graph_hierarchy_df,
|
65
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
66
|
+
)
|
67
|
+
|
68
|
+
assert rxn_edges.shape[0] == 4
|
69
|
+
assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "cat"]
|
70
|
+
assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["cat", "sub"]
|
71
|
+
|
72
|
+
# no substrate
|
73
|
+
rxn_edges = net_create_utils.format_tiered_reaction_species(
|
74
|
+
reaction_species_examples["no_substrate"],
|
75
|
+
r_id,
|
76
|
+
graph_hierarchy_df,
|
77
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
78
|
+
)
|
79
|
+
|
80
|
+
assert rxn_edges.shape[0] == 5
|
81
|
+
# stimulator -> reactant
|
82
|
+
assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim1", "cat"]
|
83
|
+
assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["stim2", "cat"]
|
84
|
+
assert rxn_edges.iloc[2][["from", "to"]].tolist() == ["inh", "cat"]
|
85
|
+
|
86
|
+
# use the surrogate model tiered layout also
|
87
|
+
|
88
|
+
graph_hierarchy_df = net_create_utils.create_graph_hierarchy_df("surrogate")
|
89
|
+
|
90
|
+
rxn_edges = net_create_utils.format_tiered_reaction_species(
|
91
|
+
reaction_species_examples["all_entities"],
|
92
|
+
r_id,
|
93
|
+
graph_hierarchy_df,
|
94
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
95
|
+
)
|
96
|
+
|
97
|
+
assert rxn_edges.shape[0] == 4
|
98
|
+
assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "sub"]
|
99
|
+
assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["sub", "cat"]
|
100
|
+
|
101
|
+
|
102
|
+
def test_drop_reactions_when_parameters(reaction_species_examples):
|
103
|
+
"""Test different drop_reactions_when parameter values and edge cases."""
|
104
|
+
|
105
|
+
r_id = "foo"
|
106
|
+
graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
|
107
|
+
|
108
|
+
# Test ALWAYS - should drop reaction regardless of tiers
|
109
|
+
edges_always = net_create_utils.format_tiered_reaction_species(
|
110
|
+
reaction_species_examples["all_entities"],
|
111
|
+
r_id,
|
112
|
+
graph_hierarchy,
|
113
|
+
DROP_REACTIONS_WHEN.ALWAYS,
|
114
|
+
)
|
115
|
+
assert r_id not in edges_always[NAPISTU_GRAPH_EDGES.FROM].values
|
116
|
+
assert r_id not in edges_always[NAPISTU_GRAPH_EDGES.TO].values
|
117
|
+
|
118
|
+
# Test EDGELIST with 2 species (should drop reaction)
|
119
|
+
edges_edgelist = net_create_utils.format_tiered_reaction_species(
|
120
|
+
reaction_species_examples["sub_and_prod"],
|
121
|
+
r_id,
|
122
|
+
graph_hierarchy,
|
123
|
+
DROP_REACTIONS_WHEN.EDGELIST,
|
124
|
+
)
|
125
|
+
assert r_id not in edges_edgelist[NAPISTU_GRAPH_EDGES.FROM].values
|
126
|
+
|
127
|
+
# Test EDGELIST with >2 species (should keep reaction)
|
128
|
+
edges_multi = net_create_utils.format_tiered_reaction_species(
|
129
|
+
reaction_species_examples["all_entities"],
|
130
|
+
r_id,
|
131
|
+
graph_hierarchy,
|
132
|
+
DROP_REACTIONS_WHEN.EDGELIST,
|
133
|
+
)
|
134
|
+
reaction_in_edges = (
|
135
|
+
r_id in edges_multi[NAPISTU_GRAPH_EDGES.FROM].values
|
136
|
+
or r_id in edges_multi[NAPISTU_GRAPH_EDGES.TO].values
|
137
|
+
)
|
138
|
+
assert reaction_in_edges
|
139
|
+
|
140
|
+
# Test invalid parameter
|
141
|
+
with pytest.raises(ValueError, match="Invalid drop_reactions"):
|
142
|
+
net_create_utils.format_tiered_reaction_species(
|
143
|
+
reaction_species_examples["sub_and_prod"],
|
144
|
+
r_id,
|
145
|
+
graph_hierarchy,
|
146
|
+
"INVALID_OPTION",
|
147
|
+
)
|
148
|
+
|
149
|
+
|
150
|
+
def test_edge_cases_and_validation(reaction_species_examples):
|
151
|
+
"""Test edge cases, empty inputs, and validation errors."""
|
152
|
+
|
153
|
+
r_id = "foo"
|
154
|
+
graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
|
155
|
+
|
156
|
+
# Test single species
|
157
|
+
edges_single = net_create_utils.format_tiered_reaction_species(
|
158
|
+
reaction_species_examples["single_species"], r_id, graph_hierarchy
|
159
|
+
)
|
160
|
+
assert edges_single.empty
|
161
|
+
|
162
|
+
# Test validation with incorrectly indexed DataFrame (should raise error)
|
163
|
+
bad_df = reaction_species_examples[
|
164
|
+
"sub_and_prod"
|
165
|
+
].reset_index() # Remove proper index
|
166
|
+
with pytest.raises(ValueError):
|
167
|
+
net_create_utils.format_tiered_reaction_species(bad_df, r_id, graph_hierarchy)
|
168
|
+
|
169
|
+
# Test activator and inhibitor only (should return empty DataFrame)
|
170
|
+
edges_ai = net_create_utils.format_tiered_reaction_species(
|
171
|
+
reaction_species_examples["activator_and_inhibitor_only"], r_id, graph_hierarchy
|
172
|
+
)
|
173
|
+
assert edges_ai.empty
|
174
|
+
|
175
|
+
|
176
|
+
def test_edgelist_should_have_one_edge():
|
177
|
+
"""EDGELIST with 2 species should create exactly 1 edge, not 2"""
|
178
|
+
|
179
|
+
r_id = "foo"
|
180
|
+
reaction_df = pd.DataFrame(
|
181
|
+
{
|
182
|
+
SBML_DFS.SBO_TERM: [
|
183
|
+
MINI_SBO_FROM_NAME[SBOTERM_NAMES.REACTANT],
|
184
|
+
MINI_SBO_FROM_NAME[SBOTERM_NAMES.PRODUCT],
|
185
|
+
],
|
186
|
+
SBML_DFS.SC_ID: ["sub", "prod"],
|
187
|
+
SBML_DFS.STOICHIOMETRY: [-1.0, 1.0],
|
188
|
+
}
|
189
|
+
).set_index(SBML_DFS.SBO_TERM)
|
190
|
+
|
191
|
+
graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
|
192
|
+
edges = net_create_utils.format_tiered_reaction_species(
|
193
|
+
reaction_df, r_id, graph_hierarchy, DROP_REACTIONS_WHEN.EDGELIST
|
194
|
+
)
|
195
|
+
|
196
|
+
# Should be 1 edge, actually gets 2
|
197
|
+
assert len(edges) == 1, f"EDGELIST should create 1 edge, got {len(edges)}"
|
198
|
+
|
199
|
+
|
200
|
+
def test_edgelist_should_not_have_reaction_as_source():
|
201
|
+
"""EDGELIST should not have reaction ID in FROM column"""
|
202
|
+
|
203
|
+
r_id = "foo"
|
204
|
+
reaction_df = pd.DataFrame(
|
205
|
+
{
|
206
|
+
SBML_DFS.SBO_TERM: [
|
207
|
+
MINI_SBO_FROM_NAME[SBOTERM_NAMES.REACTANT],
|
208
|
+
MINI_SBO_FROM_NAME[SBOTERM_NAMES.PRODUCT],
|
209
|
+
],
|
210
|
+
SBML_DFS.SC_ID: ["sub", "prod"],
|
211
|
+
SBML_DFS.STOICHIOMETRY: [-1.0, 1.0],
|
212
|
+
}
|
213
|
+
).set_index(SBML_DFS.SBO_TERM)
|
214
|
+
|
215
|
+
graph_hierarchy = net_create_utils.create_graph_hierarchy_df("regulatory")
|
216
|
+
edges = net_create_utils.format_tiered_reaction_species(
|
217
|
+
reaction_df, r_id, graph_hierarchy, DROP_REACTIONS_WHEN.EDGELIST
|
218
|
+
)
|
219
|
+
|
220
|
+
# Should not have 'foo' in FROM column, but it does
|
221
|
+
assert (
|
222
|
+
r_id not in edges["from"].values
|
223
|
+
), f"Reaction {r_id} should not appear in FROM column"
|
224
|
+
|
225
|
+
|
226
|
+
def test_should_drop_reaction(reaction_species_examples):
|
227
|
+
|
228
|
+
r_id = "foo"
|
229
|
+
|
230
|
+
graph_hierarchy_df = net_create_utils.create_graph_hierarchy_df("regulatory")
|
231
|
+
|
232
|
+
rxn_species = reaction_species_examples["sub_and_prod"]
|
233
|
+
net_create_utils._validate_sbo_indexed_rsc_stoi(rxn_species)
|
234
|
+
|
235
|
+
# map reaction species to the tiers of the graph hierarchy. higher levels point to lower levels
|
236
|
+
# same-level entries point at each other only if there is only a single tier
|
237
|
+
entities_ordered_by_tier = net_create_utils._reaction_species_to_tiers(
|
238
|
+
rxn_species, graph_hierarchy_df, r_id
|
239
|
+
)
|
240
|
+
|
241
|
+
# this is an edgeliist (just 2 entries)
|
242
|
+
assert net_create_utils._should_drop_reaction(
|
243
|
+
entities_ordered_by_tier, drop_reactions_when=DROP_REACTIONS_WHEN.EDGELIST
|
244
|
+
)
|
245
|
+
|
246
|
+
# not the same tier
|
247
|
+
assert not net_create_utils._should_drop_reaction(
|
248
|
+
entities_ordered_by_tier, drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER
|
249
|
+
)
|
250
|
+
|
251
|
+
|
252
|
+
def test_graph_hierarchy_layouts():
|
253
|
+
REQUIRED_NAMES = VALID_SBO_TERM_NAMES + [NAPISTU_GRAPH_NODE_TYPES.REACTION]
|
254
|
+
for value in VALID_GRAPH_WIRING_APPROACHES:
|
255
|
+
layout_df = net_create_utils.create_graph_hierarchy_df(value)
|
256
|
+
# all terms should be represented
|
257
|
+
missing = set(REQUIRED_NAMES).difference(
|
258
|
+
set(layout_df[NAPISTU_GRAPH_EDGES.SBO_NAME])
|
259
|
+
)
|
260
|
+
assert not missing, f"Missing SBO names in {value}: {missing}"
|
261
|
+
# all terms should be unique
|
262
|
+
duplicated = layout_df[layout_df[NAPISTU_GRAPH_EDGES.SBO_NAME].duplicated()]
|
263
|
+
assert (
|
264
|
+
duplicated.empty
|
265
|
+
), f"Duplicated SBO names in {value}: {duplicated[NAPISTU_GRAPH_EDGES.SBO_NAME].tolist()}"
|
266
|
+
# check that reaction is present and its by itself
|
267
|
+
reaction_tiers = layout_df[
|
268
|
+
layout_df[NAPISTU_GRAPH_EDGES.SBO_NAME] == NAPISTU_GRAPH_NODE_TYPES.REACTION
|
269
|
+
]["tier"].unique()
|
270
|
+
assert (
|
271
|
+
len(reaction_tiers) == 1
|
272
|
+
), f"'reaction' appears in multiple tiers in {value}: {reaction_tiers}"
|
273
|
+
reaction_tier = reaction_tiers[0]
|
274
|
+
reaction_tier_df = layout_df[layout_df["tier"] == reaction_tier]
|
275
|
+
assert (
|
276
|
+
reaction_tier_df.shape[0] == 1
|
277
|
+
and reaction_tier_df[NAPISTU_GRAPH_EDGES.SBO_NAME].iloc[0]
|
278
|
+
== NAPISTU_GRAPH_NODE_TYPES.REACTION
|
279
|
+
), f"Tier {reaction_tier} in {value} should contain only 'reaction', but contains: {reaction_tier_df[NAPISTU_GRAPH_EDGES.SBO_NAME].tolist()}"
|
280
|
+
|
281
|
+
|
282
|
+
def test_identifying_and_formatting_interactor_duos(reaction_species_examples):
|
283
|
+
|
284
|
+
# directly specify interactions as a speed up to same tier procedure
|
285
|
+
interaction_template = reaction_species_examples["valid_interactor"]
|
286
|
+
interactor_species = pd.concat(
|
287
|
+
[
|
288
|
+
interaction_template.reset_index().assign(
|
289
|
+
r_id=str(i),
|
290
|
+
sc_id=lambda df: df[SBML_DFS.SC_ID].apply(lambda x: f"r{i}_{x}"),
|
291
|
+
)
|
292
|
+
for i in range(0, 10)
|
293
|
+
]
|
294
|
+
)
|
295
|
+
|
296
|
+
invalid_interactor_template = reaction_species_examples["invalid_interactor"]
|
297
|
+
invalid_interactor_species = pd.concat(
|
298
|
+
[
|
299
|
+
invalid_interactor_template.reset_index().assign(
|
300
|
+
r_id=str(i),
|
301
|
+
sc_id=lambda df: df[SBML_DFS.SC_ID].apply(lambda x: f"r{i}_{x}"),
|
302
|
+
)
|
303
|
+
for i in range(10, 12)
|
304
|
+
]
|
305
|
+
)
|
306
|
+
|
307
|
+
reaction_species = pd.concat([interactor_species, invalid_interactor_species])
|
308
|
+
|
309
|
+
matching_r_ids = net_create_utils._find_sbo_duos(
|
310
|
+
reaction_species, MINI_SBO_FROM_NAME[SBOTERM_NAMES.INTERACTOR]
|
311
|
+
)
|
312
|
+
assert set(matching_r_ids) == {str(i) for i in range(0, 10)}
|
313
|
+
|
314
|
+
interactor_duos = reaction_species.loc[
|
315
|
+
reaction_species[SBML_DFS.R_ID].isin(matching_r_ids)
|
316
|
+
]
|
317
|
+
assert net_create_utils._interactor_duos_to_wide(interactor_duos).shape[0] == 10
|
318
|
+
|
319
|
+
|
320
|
+
def test_wire_reaction_species_mixed_interactions(reaction_species_examples):
|
321
|
+
"""
|
322
|
+
Test wire_reaction_species function with a mix of interactor and non-interactor reactions.
|
323
|
+
|
324
|
+
This test verifies that the function correctly processes:
|
325
|
+
1. Interactor pairs (processed en-masse)
|
326
|
+
2. Non-interactor reactions (processed with tiered algorithms)
|
327
|
+
3. Different wiring approaches
|
328
|
+
4. Different drop_reactions_when conditions
|
329
|
+
"""
|
330
|
+
|
331
|
+
# Create a mixed dataset with both interactor and non-interactor reactions
|
332
|
+
# Interactor reactions (should be processed en-masse)
|
333
|
+
interactor_template = reaction_species_examples["valid_interactor"]
|
334
|
+
interactor_species = pd.concat(
|
335
|
+
[
|
336
|
+
interactor_template.reset_index().assign(
|
337
|
+
r_id=f"interactor_{i}",
|
338
|
+
sc_id=lambda df: df[SBML_DFS.SC_ID].apply(
|
339
|
+
lambda x: f"interactor_{i}_{x}"
|
340
|
+
),
|
341
|
+
)
|
342
|
+
for i in range(3) # 3 interactor reactions
|
343
|
+
]
|
344
|
+
)
|
345
|
+
|
346
|
+
# Non-interactor reactions (should be processed with tiered algorithms)
|
347
|
+
non_interactor_species = pd.concat(
|
348
|
+
[
|
349
|
+
reaction_species_examples["sub_and_prod"]
|
350
|
+
.reset_index()
|
351
|
+
.assign(
|
352
|
+
r_id=f"reaction_{i}",
|
353
|
+
sc_id=lambda df: df[SBML_DFS.SC_ID].apply(
|
354
|
+
lambda x: f"reaction_{i}_{x}"
|
355
|
+
),
|
356
|
+
)
|
357
|
+
for i in range(2) # 2 substrate-product reactions
|
358
|
+
]
|
359
|
+
)
|
360
|
+
|
361
|
+
# Add a complex reaction with multiple entity types
|
362
|
+
complex_reaction = (
|
363
|
+
reaction_species_examples["all_entities"]
|
364
|
+
.reset_index()
|
365
|
+
.assign(
|
366
|
+
r_id="complex_reaction",
|
367
|
+
sc_id=lambda df: df[SBML_DFS.SC_ID].apply(lambda x: f"complex_{x}"),
|
368
|
+
)
|
369
|
+
)
|
370
|
+
|
371
|
+
# Combine all reaction species
|
372
|
+
all_reaction_species = pd.concat(
|
373
|
+
[interactor_species, non_interactor_species, complex_reaction]
|
374
|
+
)
|
375
|
+
|
376
|
+
# Test with regulatory wiring approach
|
377
|
+
edges_regulatory = net_create_utils.wire_reaction_species(
|
378
|
+
all_reaction_species,
|
379
|
+
wiring_approach="regulatory",
|
380
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
381
|
+
)
|
382
|
+
|
383
|
+
# Verify the output structure
|
384
|
+
assert not edges_regulatory.empty
|
385
|
+
required_columns = [
|
386
|
+
NAPISTU_GRAPH_EDGES.FROM,
|
387
|
+
NAPISTU_GRAPH_EDGES.TO,
|
388
|
+
NAPISTU_GRAPH_EDGES.STOICHIOMETRY,
|
389
|
+
NAPISTU_GRAPH_EDGES.SBO_TERM,
|
390
|
+
SBML_DFS.R_ID,
|
391
|
+
]
|
392
|
+
|
393
|
+
for col in required_columns:
|
394
|
+
assert col in edges_regulatory.columns, f"Missing required column: {col}"
|
395
|
+
|
396
|
+
# Check that interactor reactions were processed correctly
|
397
|
+
interactor_edges = edges_regulatory[
|
398
|
+
edges_regulatory[SBML_DFS.R_ID].str.startswith("interactor_")
|
399
|
+
]
|
400
|
+
assert (
|
401
|
+
len(interactor_edges) == 3
|
402
|
+
), f"Expected 3 interactor edges, got {len(interactor_edges)}"
|
403
|
+
|
404
|
+
# Check that non-interactor reactions were processed correctly
|
405
|
+
reaction_edges = edges_regulatory[
|
406
|
+
edges_regulatory[SBML_DFS.R_ID].str.startswith("reaction_")
|
407
|
+
]
|
408
|
+
assert (
|
409
|
+
len(reaction_edges) == 4
|
410
|
+
), f"Expected 4 reaction edges, got {len(reaction_edges)}"
|
411
|
+
|
412
|
+
# Check that complex reaction was processed correctly
|
413
|
+
complex_edges = edges_regulatory[
|
414
|
+
edges_regulatory[SBML_DFS.R_ID] == "complex_reaction"
|
415
|
+
]
|
416
|
+
assert (
|
417
|
+
len(complex_edges) == 4
|
418
|
+
), f"Expected 4 complex reaction edges, got {len(complex_edges)}"
|
419
|
+
|
420
|
+
# Test with different drop_reactions_when condition
|
421
|
+
edges_always_drop = net_create_utils.wire_reaction_species(
|
422
|
+
all_reaction_species,
|
423
|
+
wiring_approach="regulatory",
|
424
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.ALWAYS,
|
425
|
+
)
|
426
|
+
|
427
|
+
# With ALWAYS drop, reaction IDs should not appear in FROM or TO columns
|
428
|
+
reaction_ids = [
|
429
|
+
"interactor_0",
|
430
|
+
"interactor_1",
|
431
|
+
"interactor_2",
|
432
|
+
"reaction_0",
|
433
|
+
"reaction_1",
|
434
|
+
"complex_reaction",
|
435
|
+
]
|
436
|
+
for r_id in reaction_ids:
|
437
|
+
assert (
|
438
|
+
r_id not in edges_always_drop[NAPISTU_GRAPH_EDGES.FROM].values
|
439
|
+
), f"Reaction {r_id} should not be in FROM column"
|
440
|
+
assert (
|
441
|
+
r_id not in edges_always_drop[NAPISTU_GRAPH_EDGES.TO].values
|
442
|
+
), f"Reaction {r_id} should not be in TO column"
|
443
|
+
|
444
|
+
# Test with EDGELIST drop condition
|
445
|
+
edges_edgelist = net_create_utils.wire_reaction_species(
|
446
|
+
all_reaction_species,
|
447
|
+
wiring_approach="regulatory",
|
448
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.EDGELIST,
|
449
|
+
)
|
450
|
+
|
451
|
+
# Simple reactions (2 species) should not have reaction IDs, but complex reactions should
|
452
|
+
simple_reaction_ids = ["reaction_0", "reaction_1"]
|
453
|
+
complex_reaction_id = "complex_reaction"
|
454
|
+
|
455
|
+
for r_id in simple_reaction_ids:
|
456
|
+
assert (
|
457
|
+
r_id not in edges_edgelist[NAPISTU_GRAPH_EDGES.FROM].values
|
458
|
+
), f"Simple reaction {r_id} should not be in FROM column"
|
459
|
+
assert (
|
460
|
+
r_id not in edges_edgelist[NAPISTU_GRAPH_EDGES.TO].values
|
461
|
+
), f"Simple reaction {r_id} should not be in TO column"
|
462
|
+
|
463
|
+
# Complex reaction should still have reaction ID in edges
|
464
|
+
complex_in_edges = (
|
465
|
+
complex_reaction_id in edges_edgelist[NAPISTU_GRAPH_EDGES.FROM].values
|
466
|
+
or complex_reaction_id in edges_edgelist[NAPISTU_GRAPH_EDGES.TO].values
|
467
|
+
)
|
468
|
+
assert (
|
469
|
+
complex_in_edges
|
470
|
+
), f"Complex reaction {complex_reaction_id} should appear in edges"
|
471
|
+
|
472
|
+
# Test edge case: only interactor reactions
|
473
|
+
only_interactors = interactor_species
|
474
|
+
edges_only_interactors = net_create_utils.wire_reaction_species(
|
475
|
+
only_interactors,
|
476
|
+
wiring_approach="regulatory",
|
477
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
478
|
+
)
|
479
|
+
|
480
|
+
assert not edges_only_interactors.empty
|
481
|
+
assert (
|
482
|
+
len(edges_only_interactors) == 3
|
483
|
+
), f"Expected 3 edges for only interactors, got {len(edges_only_interactors)}"
|
484
|
+
|
485
|
+
# Test edge case: only non-interactor reactions
|
486
|
+
only_reactions = pd.concat([non_interactor_species, complex_reaction])
|
487
|
+
edges_only_reactions = net_create_utils.wire_reaction_species(
|
488
|
+
only_reactions,
|
489
|
+
wiring_approach="regulatory",
|
490
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
491
|
+
)
|
492
|
+
|
493
|
+
print(edges_only_reactions)
|
494
|
+
|
495
|
+
assert not edges_only_reactions.empty
|
496
|
+
assert (
|
497
|
+
len(edges_only_reactions) == 8
|
498
|
+
), f"Expected 8 edges for only reactions, got {len(edges_only_reactions)}"
|
499
|
+
|
500
|
+
|
501
|
+
def test_wire_reaction_species_validation_errors():
|
502
|
+
"""Test wire_reaction_species function with invalid inputs."""
|
503
|
+
|
504
|
+
# Test with invalid wiring approach
|
505
|
+
reaction_species = pd.DataFrame(
|
506
|
+
{
|
507
|
+
SBML_DFS.R_ID: ["R1"],
|
508
|
+
SBML_DFS.SC_ID: ["A"],
|
509
|
+
SBML_DFS.STOICHIOMETRY: [0],
|
510
|
+
SBML_DFS.SBO_TERM: ["SBO:0000336"], # interactor
|
511
|
+
}
|
512
|
+
)
|
513
|
+
|
514
|
+
with pytest.raises(ValueError, match="is not a valid wiring approach"):
|
515
|
+
net_create_utils.wire_reaction_species(
|
516
|
+
reaction_species,
|
517
|
+
wiring_approach="invalid_approach",
|
518
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
519
|
+
)
|
520
|
+
|
521
|
+
# Test with invalid SBO terms
|
522
|
+
invalid_sbo_species = pd.DataFrame(
|
523
|
+
{
|
524
|
+
SBML_DFS.R_ID: ["R1"],
|
525
|
+
SBML_DFS.SC_ID: ["A"],
|
526
|
+
SBML_DFS.STOICHIOMETRY: [0],
|
527
|
+
SBML_DFS.SBO_TERM: ["INVALID_SBO_TERM"],
|
528
|
+
}
|
529
|
+
)
|
530
|
+
|
531
|
+
with pytest.raises(
|
532
|
+
ValueError, match="Some reaction species have unusable SBO terms"
|
533
|
+
):
|
534
|
+
net_create_utils.wire_reaction_species(
|
535
|
+
invalid_sbo_species,
|
536
|
+
wiring_approach="regulatory",
|
537
|
+
drop_reactions_when=DROP_REACTIONS_WHEN.SAME_TIER,
|
538
|
+
)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import igraph as ig
|
2
|
+
import logging
|
3
|
+
|
4
|
+
from napistu.network.ng_core import NapistuGraph
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
|
9
|
+
def test_remove_isolated_vertices():
|
10
|
+
"""Test removing isolated vertices from a graph."""
|
11
|
+
|
12
|
+
g = ig.Graph()
|
13
|
+
g.add_vertices(5, attributes={"name": ["A", "B", "C", "D", "E"]})
|
14
|
+
g.add_edges([(0, 1), (2, 3)]) # A-B, C-D connected; E isolated
|
15
|
+
|
16
|
+
napstu_graph = NapistuGraph.from_igraph(g)
|
17
|
+
napstu_graph.remove_isolated_vertices()
|
18
|
+
assert napstu_graph.vcount() == 4 # Should have 4 vertices after removing E
|
19
|
+
assert "E" not in [v["name"] for v in napstu_graph.vs] # E should be gone
|
tests/test_network_ng_utils.py
CHANGED
tests/test_network_precompute.py
CHANGED
@@ -7,6 +7,7 @@ import tempfile
|
|
7
7
|
import numpy as np
|
8
8
|
import pandas as pd
|
9
9
|
from napistu import sbml_dfs_core
|
10
|
+
from napistu import utils
|
10
11
|
from napistu.ingestion import sbml
|
11
12
|
from napistu.network import neighborhoods
|
12
13
|
from napistu.network import net_create
|
@@ -18,12 +19,12 @@ sbml_path = os.path.join(test_path, "test_data", "reactome_glucose_metabolism.sb
|
|
18
19
|
if not os.path.isfile(sbml_path):
|
19
20
|
raise ValueError(f"{sbml_path} not found")
|
20
21
|
|
21
|
-
sbml_model = sbml.SBML(sbml_path)
|
22
|
+
sbml_model = sbml.SBML(sbml_path)
|
22
23
|
sbml_dfs = sbml_dfs_core.SBML_dfs(sbml_model)
|
23
24
|
sbml_dfs.validate()
|
24
25
|
|
25
26
|
napistu_graph = net_create.process_napistu_graph(
|
26
|
-
sbml_dfs,
|
27
|
+
sbml_dfs, wiring_approach="bipartite", directed=True, weighting_strategy="topology"
|
27
28
|
)
|
28
29
|
|
29
30
|
# number of species to include when finding all x all paths
|
@@ -263,10 +264,10 @@ def test_precomputed_distances_serialization():
|
|
263
264
|
|
264
265
|
try:
|
265
266
|
# Test serialization
|
266
|
-
|
267
|
+
utils.save_parquet(original_df, temp_path)
|
267
268
|
|
268
269
|
# Test deserialization
|
269
|
-
loaded_df =
|
270
|
+
loaded_df = utils.load_parquet(temp_path)
|
270
271
|
|
271
272
|
# Validate that the loaded DataFrame is identical to the original
|
272
273
|
pd.testing.assert_frame_equal(original_df, loaded_df, check_like=True)
|