napistu 0.2.5.dev7__py3-none-any.whl → 0.3.1__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 (107) hide show
  1. napistu/__main__.py +126 -96
  2. napistu/constants.py +35 -41
  3. napistu/context/__init__.py +10 -0
  4. napistu/context/discretize.py +462 -0
  5. napistu/context/filtering.py +387 -0
  6. napistu/gcs/__init__.py +1 -1
  7. napistu/identifiers.py +74 -15
  8. napistu/indices.py +68 -0
  9. napistu/ingestion/__init__.py +1 -1
  10. napistu/ingestion/bigg.py +47 -62
  11. napistu/ingestion/constants.py +18 -133
  12. napistu/ingestion/gtex.py +113 -0
  13. napistu/ingestion/hpa.py +147 -0
  14. napistu/ingestion/sbml.py +0 -97
  15. napistu/ingestion/string.py +2 -2
  16. napistu/matching/__init__.py +10 -0
  17. napistu/matching/constants.py +18 -0
  18. napistu/matching/interactions.py +518 -0
  19. napistu/matching/mount.py +529 -0
  20. napistu/matching/species.py +510 -0
  21. napistu/mcp/__init__.py +7 -4
  22. napistu/mcp/__main__.py +128 -72
  23. napistu/mcp/client.py +16 -25
  24. napistu/mcp/codebase.py +201 -145
  25. napistu/mcp/component_base.py +170 -0
  26. napistu/mcp/config.py +223 -0
  27. napistu/mcp/constants.py +45 -2
  28. napistu/mcp/documentation.py +253 -136
  29. napistu/mcp/documentation_utils.py +13 -48
  30. napistu/mcp/execution.py +372 -305
  31. napistu/mcp/health.py +47 -65
  32. napistu/mcp/profiles.py +10 -6
  33. napistu/mcp/server.py +161 -80
  34. napistu/mcp/tutorials.py +139 -87
  35. napistu/modify/__init__.py +1 -1
  36. napistu/modify/gaps.py +1 -1
  37. napistu/network/__init__.py +1 -1
  38. napistu/network/constants.py +101 -34
  39. napistu/network/data_handling.py +388 -0
  40. napistu/network/ig_utils.py +351 -0
  41. napistu/network/napistu_graph_core.py +354 -0
  42. napistu/network/neighborhoods.py +40 -40
  43. napistu/network/net_create.py +373 -309
  44. napistu/network/net_propagation.py +47 -19
  45. napistu/network/{net_utils.py → ng_utils.py} +124 -272
  46. napistu/network/paths.py +67 -51
  47. napistu/network/precompute.py +11 -11
  48. napistu/ontologies/__init__.py +10 -0
  49. napistu/ontologies/constants.py +129 -0
  50. napistu/ontologies/dogma.py +243 -0
  51. napistu/ontologies/genodexito.py +649 -0
  52. napistu/ontologies/mygene.py +369 -0
  53. napistu/ontologies/renaming.py +198 -0
  54. napistu/rpy2/__init__.py +229 -86
  55. napistu/rpy2/callr.py +47 -77
  56. napistu/rpy2/constants.py +24 -23
  57. napistu/rpy2/rids.py +61 -648
  58. napistu/sbml_dfs_core.py +587 -222
  59. napistu/scverse/__init__.py +15 -0
  60. napistu/scverse/constants.py +28 -0
  61. napistu/scverse/loading.py +727 -0
  62. napistu/utils.py +118 -10
  63. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/METADATA +8 -3
  64. napistu-0.3.1.dist-info/RECORD +133 -0
  65. tests/conftest.py +22 -0
  66. tests/test_context_discretize.py +56 -0
  67. tests/test_context_filtering.py +267 -0
  68. tests/test_identifiers.py +100 -0
  69. tests/test_indices.py +65 -0
  70. tests/{test_edgelist.py → test_ingestion_napistu_edgelist.py} +2 -2
  71. tests/test_matching_interactions.py +108 -0
  72. tests/test_matching_mount.py +305 -0
  73. tests/test_matching_species.py +394 -0
  74. tests/test_mcp_config.py +193 -0
  75. tests/test_mcp_documentation_utils.py +12 -3
  76. tests/test_mcp_server.py +156 -19
  77. tests/test_network_data_handling.py +397 -0
  78. tests/test_network_ig_utils.py +23 -0
  79. tests/test_network_neighborhoods.py +19 -0
  80. tests/test_network_net_create.py +459 -0
  81. tests/test_network_ng_utils.py +30 -0
  82. tests/test_network_paths.py +56 -0
  83. tests/{test_precomputed_distances.py → test_network_precompute.py} +8 -6
  84. tests/test_ontologies_genodexito.py +58 -0
  85. tests/test_ontologies_mygene.py +39 -0
  86. tests/test_ontologies_renaming.py +110 -0
  87. tests/test_rpy2_callr.py +79 -0
  88. tests/test_rpy2_init.py +151 -0
  89. tests/test_sbml.py +0 -31
  90. tests/test_sbml_dfs_core.py +134 -10
  91. tests/test_scverse_loading.py +778 -0
  92. tests/test_set_coverage.py +2 -2
  93. tests/test_utils.py +121 -1
  94. napistu/mechanism_matching.py +0 -1353
  95. napistu/rpy2/netcontextr.py +0 -467
  96. napistu-0.2.5.dev7.dist-info/RECORD +0 -98
  97. tests/test_igraph.py +0 -367
  98. tests/test_mechanism_matching.py +0 -784
  99. tests/test_net_utils.py +0 -149
  100. tests/test_netcontextr.py +0 -105
  101. tests/test_rpy2.py +0 -61
  102. /napistu/ingestion/{cpr_edgelist.py → napistu_edgelist.py} +0 -0
  103. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/WHEEL +0 -0
  104. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/entry_points.txt +0 -0
  105. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/licenses/LICENSE +0 -0
  106. {napistu-0.2.5.dev7.dist-info → napistu-0.3.1.dist-info}/top_level.txt +0 -0
  107. /tests/{test_obo.py → test_ingestion_obo.py} +0 -0
@@ -0,0 +1,459 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import pytest
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from napistu import sbml_dfs_core
10
+ from napistu.ingestion import sbml
11
+ from napistu.network import net_create
12
+ from napistu.network import ng_utils
13
+ from napistu.constants import MINI_SBO_FROM_NAME
14
+ from napistu.constants import SBML_DFS
15
+ from napistu.network.constants import DEFAULT_WT_TRANS
16
+ from napistu.network.constants import WEIGHTING_SPEC
17
+
18
+
19
+ test_path = os.path.abspath(os.path.join(__file__, os.pardir))
20
+ test_data = os.path.join(test_path, "test_data")
21
+
22
+ sbml_path = os.path.join(test_data, "R-HSA-1237044.sbml")
23
+ sbml_model = sbml.SBML(sbml_path).model
24
+ sbml_dfs = sbml_dfs_core.SBML_dfs(sbml_model)
25
+
26
+
27
+ @pytest.fixture
28
+ def reaction_species_examples(sbml_dfs):
29
+ """
30
+ Pytest fixture providing a dictionary of example reaction species DataFrames for various test cases.
31
+ """
32
+ r_id = sbml_dfs.reactions.index[0]
33
+ d = dict()
34
+ d["valid_interactor"] = pd.DataFrame(
35
+ {
36
+ "r_id": [r_id, r_id],
37
+ "sbo_term": [
38
+ MINI_SBO_FROM_NAME["interactor"],
39
+ MINI_SBO_FROM_NAME["interactor"],
40
+ ],
41
+ "sc_id": ["sc1", "sc2"],
42
+ "stoichiometry": [0, 0],
43
+ }
44
+ ).set_index(["r_id", "sbo_term"])
45
+ d["invalid_interactor"] = pd.DataFrame(
46
+ {
47
+ "r_id": [r_id, r_id],
48
+ "sbo_term": [
49
+ MINI_SBO_FROM_NAME["interactor"],
50
+ MINI_SBO_FROM_NAME["product"],
51
+ ],
52
+ "sc_id": ["sc1", "sc2"],
53
+ "stoichiometry": [0, 0],
54
+ }
55
+ ).set_index(["r_id", "sbo_term"])
56
+ d["sub_and_prod"] = pd.DataFrame(
57
+ {
58
+ "r_id": [r_id, r_id],
59
+ "sbo_term": [MINI_SBO_FROM_NAME["reactant"], MINI_SBO_FROM_NAME["product"]],
60
+ "sc_id": ["sub", "prod"],
61
+ "stoichiometry": [-1, 1],
62
+ }
63
+ ).set_index(["r_id", "sbo_term"])
64
+ d["stimulator"] = pd.DataFrame(
65
+ {
66
+ "r_id": [r_id, r_id, r_id],
67
+ "sbo_term": [
68
+ MINI_SBO_FROM_NAME["reactant"],
69
+ MINI_SBO_FROM_NAME["product"],
70
+ MINI_SBO_FROM_NAME["stimulator"],
71
+ ],
72
+ "sc_id": ["sub", "prod", "stim"],
73
+ "stoichiometry": [-1, 1, 0],
74
+ }
75
+ ).set_index(["r_id", "sbo_term"])
76
+ d["all_entities"] = pd.DataFrame(
77
+ {
78
+ "r_id": [r_id, r_id, r_id, r_id],
79
+ "sbo_term": [
80
+ MINI_SBO_FROM_NAME["reactant"],
81
+ MINI_SBO_FROM_NAME["product"],
82
+ MINI_SBO_FROM_NAME["stimulator"],
83
+ MINI_SBO_FROM_NAME["catalyst"],
84
+ ],
85
+ "sc_id": ["sub", "prod", "stim", "cat"],
86
+ "stoichiometry": [-1, 1, 0, 0],
87
+ }
88
+ ).set_index(["r_id", "sbo_term"])
89
+ d["no_substrate"] = pd.DataFrame(
90
+ {
91
+ "r_id": [r_id, r_id, r_id, r_id, r_id],
92
+ "sbo_term": [
93
+ MINI_SBO_FROM_NAME["product"],
94
+ MINI_SBO_FROM_NAME["stimulator"],
95
+ MINI_SBO_FROM_NAME["stimulator"],
96
+ MINI_SBO_FROM_NAME["inhibitor"],
97
+ MINI_SBO_FROM_NAME["catalyst"],
98
+ ],
99
+ "sc_id": ["prod", "stim1", "stim2", "inh", "cat"],
100
+ "stoichiometry": [1, 0, 0, 0, 0],
101
+ }
102
+ ).set_index(["r_id", "sbo_term"])
103
+
104
+ return r_id, d
105
+
106
+
107
+ def test_create_napistu_graph():
108
+ _ = net_create.create_napistu_graph(sbml_dfs, graph_type="bipartite")
109
+ _ = net_create.create_napistu_graph(sbml_dfs, graph_type="regulatory")
110
+ _ = net_create.create_napistu_graph(sbml_dfs, graph_type="surrogate")
111
+
112
+
113
+ def test_create_napistu_graph_edge_reversed():
114
+ """Test that edge_reversed=True properly reverses edges in the graph for all graph types."""
115
+ # Test each graph type
116
+ for graph_type in ["bipartite", "regulatory", "surrogate"]:
117
+ # Create graphs with and without edge reversal
118
+ normal_graph = net_create.create_napistu_graph(
119
+ sbml_dfs, graph_type=graph_type, directed=True, edge_reversed=False
120
+ )
121
+ reversed_graph = net_create.create_napistu_graph(
122
+ sbml_dfs, graph_type=graph_type, directed=True, edge_reversed=True
123
+ )
124
+
125
+ # Get edge dataframes for comparison
126
+ normal_edges = normal_graph.get_edge_dataframe()
127
+ reversed_edges = reversed_graph.get_edge_dataframe()
128
+
129
+ # Verify we have edges to test
130
+ assert len(normal_edges) > 0, f"No edges found in {graph_type} graph"
131
+ assert len(normal_edges) == len(
132
+ reversed_edges
133
+ ), f"Edge count mismatch in {graph_type} graph"
134
+
135
+ # Test edge reversal
136
+ # Check a few edges to verify from/to are swapped
137
+ for i in range(min(5, len(normal_edges))):
138
+ # Check from/to are swapped
139
+ assert (
140
+ normal_edges.iloc[i]["from"] == reversed_edges.iloc[i]["to"]
141
+ ), f"From/to not properly swapped in {graph_type} graph"
142
+ assert (
143
+ normal_edges.iloc[i]["to"] == reversed_edges.iloc[i]["from"]
144
+ ), f"From/to not properly swapped in {graph_type} graph"
145
+
146
+ # Check stoichiometry is negated
147
+ assert (
148
+ normal_edges.iloc[i]["stoichiometry"]
149
+ == -reversed_edges.iloc[i]["stoichiometry"]
150
+ ), f"Stoichiometry not properly negated in {graph_type} graph"
151
+
152
+ # Check direction attributes are properly swapped
153
+ if normal_edges.iloc[i]["direction"] == "forward":
154
+ assert (
155
+ reversed_edges.iloc[i]["direction"] == "reverse"
156
+ ), f"Direction not properly reversed (forward->reverse) in {graph_type} graph"
157
+ elif normal_edges.iloc[i]["direction"] == "reverse":
158
+ assert (
159
+ reversed_edges.iloc[i]["direction"] == "forward"
160
+ ), f"Direction not properly reversed (reverse->forward) in {graph_type} graph"
161
+
162
+ # Check parents/children are swapped
163
+ assert (
164
+ normal_edges.iloc[i]["sc_parents"]
165
+ == reversed_edges.iloc[i]["sc_children"]
166
+ ), f"Parents/children not properly swapped in {graph_type} graph"
167
+ assert (
168
+ normal_edges.iloc[i]["sc_children"]
169
+ == reversed_edges.iloc[i]["sc_parents"]
170
+ ), f"Parents/children not properly swapped in {graph_type} graph"
171
+
172
+
173
+ def test_create_napistu_graph_none_attrs():
174
+ # Should not raise when reaction_graph_attrs is None
175
+ _ = net_create.create_napistu_graph(
176
+ sbml_dfs, reaction_graph_attrs=None, graph_type="bipartite"
177
+ )
178
+
179
+
180
+ def test_process_napistu_graph_none_attrs():
181
+ # Should not raise when reaction_graph_attrs is None
182
+ _ = net_create.process_napistu_graph(sbml_dfs, reaction_graph_attrs=None)
183
+
184
+
185
+ @pytest.mark.skip_on_windows
186
+ def test_igraph_loading():
187
+ # test read/write of an igraph network
188
+ directeds = [True, False]
189
+ graph_types = ["bipartite", "regulatory"]
190
+
191
+ ng_utils.export_networks(
192
+ sbml_dfs,
193
+ model_prefix="tmp",
194
+ outdir="/tmp",
195
+ directeds=directeds,
196
+ graph_types=graph_types,
197
+ )
198
+
199
+ for graph_type in graph_types:
200
+ for directed in directeds:
201
+ import_pkl_path = ng_utils._create_network_save_string(
202
+ model_prefix="tmp",
203
+ outdir="/tmp",
204
+ directed=directed,
205
+ graph_type=graph_type,
206
+ )
207
+ network_graph = ng_utils.read_network_pkl(
208
+ model_prefix="tmp",
209
+ network_dir="/tmp",
210
+ directed=directed,
211
+ graph_type=graph_type,
212
+ )
213
+
214
+ assert network_graph.is_directed() == directed
215
+ # cleanup
216
+ os.unlink(import_pkl_path)
217
+
218
+
219
+ def test_format_interactors(reaction_species_examples):
220
+ r_id, reaction_species_examples_dict = reaction_species_examples
221
+ # interactions are formatted
222
+
223
+ graph_hierarchy_df = net_create._create_graph_hierarchy_df("regulatory")
224
+
225
+ assert (
226
+ net_create._format_tiered_reaction_species(
227
+ r_id,
228
+ reaction_species_examples_dict["valid_interactor"],
229
+ sbml_dfs,
230
+ graph_hierarchy_df,
231
+ ).shape[0]
232
+ == 1
233
+ )
234
+
235
+ print("Re-enable test once Issue #102 is solved")
236
+
237
+ # catch error from invalid interactor specification
238
+ # with pytest.raises(ValueError) as excinfo:
239
+ # net_create._format_tiered_reaction_species(
240
+ # r_id, reaction_species_examples_dict["invalid_interactor"], sbml_dfs
241
+ # )
242
+ # assert str(excinfo.value).startswith("Invalid combinations of SBO_terms")
243
+
244
+ # simple reaction with just substrates and products
245
+ assert (
246
+ net_create._format_tiered_reaction_species(
247
+ r_id,
248
+ reaction_species_examples_dict["sub_and_prod"],
249
+ sbml_dfs,
250
+ graph_hierarchy_df,
251
+ ).shape[0]
252
+ == 2
253
+ )
254
+
255
+ # add a stimulator (activator)
256
+ rxn_edges = net_create._format_tiered_reaction_species(
257
+ r_id, reaction_species_examples_dict["stimulator"], sbml_dfs, graph_hierarchy_df
258
+ )
259
+
260
+ assert rxn_edges.shape[0] == 3
261
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "sub"]
262
+
263
+ # add catalyst + stimulator
264
+ rxn_edges = net_create._format_tiered_reaction_species(
265
+ r_id,
266
+ reaction_species_examples_dict["all_entities"],
267
+ sbml_dfs,
268
+ graph_hierarchy_df,
269
+ )
270
+
271
+ assert rxn_edges.shape[0] == 4
272
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "cat"]
273
+ assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["cat", "sub"]
274
+
275
+ # no substrate
276
+ rxn_edges = net_create._format_tiered_reaction_species(
277
+ r_id,
278
+ reaction_species_examples_dict["no_substrate"],
279
+ sbml_dfs,
280
+ graph_hierarchy_df,
281
+ )
282
+
283
+ assert rxn_edges.shape[0] == 5
284
+ # stimulator -> reactant
285
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim1", "cat"]
286
+ assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["stim2", "cat"]
287
+ assert rxn_edges.iloc[2][["from", "to"]].tolist() == ["inh", "cat"]
288
+
289
+ # use the surrogate model tiered layout also
290
+
291
+ graph_hierarchy_df = net_create._create_graph_hierarchy_df("surrogate")
292
+
293
+ rxn_edges = net_create._format_tiered_reaction_species(
294
+ r_id,
295
+ reaction_species_examples_dict["all_entities"],
296
+ sbml_dfs,
297
+ graph_hierarchy_df,
298
+ )
299
+
300
+ assert rxn_edges.shape[0] == 4
301
+ assert rxn_edges.iloc[0][["from", "to"]].tolist() == ["stim", "sub"]
302
+ assert rxn_edges.iloc[1][["from", "to"]].tolist() == ["sub", "cat"]
303
+
304
+
305
+ def test_reverse_network_edges(reaction_species_examples):
306
+ r_id, reaction_species_examples_dict = reaction_species_examples
307
+
308
+ graph_hierarchy_df = net_create._create_graph_hierarchy_df("regulatory")
309
+
310
+ rxn_edges = net_create._format_tiered_reaction_species(
311
+ r_id,
312
+ reaction_species_examples_dict["all_entities"],
313
+ sbml_dfs,
314
+ graph_hierarchy_df,
315
+ )
316
+ augmented_network_edges = rxn_edges.assign(r_isreversible=True)
317
+ augmented_network_edges["sc_parents"] = range(0, augmented_network_edges.shape[0])
318
+ augmented_network_edges["sc_children"] = range(
319
+ augmented_network_edges.shape[0], 0, -1
320
+ )
321
+
322
+ assert net_create._reverse_network_edges(augmented_network_edges).shape[0] == 2
323
+
324
+
325
+ def test_entity_validation():
326
+ # Test basic validation
327
+ entity_attrs = {"table": "reactions", "variable": "foo"}
328
+ assert net_create._EntityAttrValidator(**entity_attrs).model_dump() == {
329
+ **entity_attrs,
330
+ **{"trans": DEFAULT_WT_TRANS},
331
+ }
332
+
333
+ # Test validation with custom transformations
334
+ custom_transformations = {
335
+ "nlog10": lambda x: -np.log10(x),
336
+ "square": lambda x: x**2,
337
+ }
338
+
339
+ # Test valid custom transformation
340
+ entity_attrs_custom = {
341
+ "attr1": {
342
+ WEIGHTING_SPEC.TABLE: "reactions",
343
+ WEIGHTING_SPEC.VARIABLE: "foo",
344
+ WEIGHTING_SPEC.TRANSFORMATION: "nlog10",
345
+ },
346
+ "attr2": {
347
+ WEIGHTING_SPEC.TABLE: "species",
348
+ WEIGHTING_SPEC.VARIABLE: "bar",
349
+ WEIGHTING_SPEC.TRANSFORMATION: "square",
350
+ },
351
+ }
352
+ # Should not raise any errors
353
+ net_create._validate_entity_attrs(
354
+ entity_attrs_custom, custom_transformations=custom_transformations
355
+ )
356
+
357
+ # Test invalid transformation
358
+ entity_attrs_invalid = {
359
+ "attr1": {
360
+ WEIGHTING_SPEC.TABLE: "reactions",
361
+ WEIGHTING_SPEC.VARIABLE: "foo",
362
+ WEIGHTING_SPEC.TRANSFORMATION: "invalid_trans",
363
+ }
364
+ }
365
+ with pytest.raises(ValueError) as excinfo:
366
+ net_create._validate_entity_attrs(
367
+ entity_attrs_invalid, custom_transformations=custom_transformations
368
+ )
369
+ assert "transformation 'invalid_trans' was not defined" in str(excinfo.value)
370
+
371
+ # Test with validate_transformations=False
372
+ # Should not raise any errors even with invalid transformation
373
+ net_create._validate_entity_attrs(
374
+ entity_attrs_invalid, validate_transformations=False
375
+ )
376
+
377
+ # Test with non-dict input
378
+ with pytest.raises(AssertionError) as excinfo:
379
+ net_create._validate_entity_attrs(["not", "a", "dict"])
380
+ assert "entity_attrs must be a dictionary" in str(excinfo.value)
381
+
382
+
383
+ def test_pluck_entity_data_species_identity(sbml_dfs):
384
+ # Take first 10 species IDs
385
+ species_ids = sbml_dfs.species.index[:10]
386
+ # Create mock data with explicit dtype to ensure cross-platform consistency
387
+ # Fix for issue-42: Use explicit dtypes to avoid platform-specific dtype differences
388
+ # between Windows (int32) and macOS/Linux (int64)
389
+ mock_df = pd.DataFrame(
390
+ {
391
+ "string_col": [f"str_{i}" for i in range(10)],
392
+ "mixed_col": np.arange(-5, 5, dtype=np.int64), # Explicitly use int64
393
+ "ones_col": np.ones(10, dtype=np.float64), # Explicitly use float64
394
+ "squared_col": np.arange(10, dtype=np.int64), # Explicitly use int64
395
+ },
396
+ index=species_ids,
397
+ )
398
+ # Assign to species_data
399
+ sbml_dfs.species_data["mock_table"] = mock_df
400
+
401
+ # Custom transformation: square
402
+ def square(x):
403
+ return x**2
404
+
405
+ custom_transformations = {"square": square}
406
+ # Create graph_attrs for species
407
+ graph_attrs = {
408
+ "species": {
409
+ "string_col": {
410
+ WEIGHTING_SPEC.TABLE: "mock_table",
411
+ WEIGHTING_SPEC.VARIABLE: "string_col",
412
+ WEIGHTING_SPEC.TRANSFORMATION: "identity",
413
+ },
414
+ "mixed_col": {
415
+ WEIGHTING_SPEC.TABLE: "mock_table",
416
+ WEIGHTING_SPEC.VARIABLE: "mixed_col",
417
+ WEIGHTING_SPEC.TRANSFORMATION: "identity",
418
+ },
419
+ "ones_col": {
420
+ WEIGHTING_SPEC.TABLE: "mock_table",
421
+ WEIGHTING_SPEC.VARIABLE: "ones_col",
422
+ WEIGHTING_SPEC.TRANSFORMATION: "identity",
423
+ },
424
+ "squared_col": {
425
+ WEIGHTING_SPEC.TABLE: "mock_table",
426
+ WEIGHTING_SPEC.VARIABLE: "squared_col",
427
+ WEIGHTING_SPEC.TRANSFORMATION: "square",
428
+ },
429
+ }
430
+ }
431
+ # Call pluck_entity_data with custom transformation
432
+ result = net_create.pluck_entity_data(
433
+ sbml_dfs, graph_attrs, "species", custom_transformations=custom_transformations
434
+ )
435
+ # Check output
436
+ assert isinstance(result, pd.DataFrame)
437
+ assert set(result.columns) == {"string_col", "mixed_col", "ones_col", "squared_col"}
438
+ assert list(result.index) == list(species_ids)
439
+ # Check values
440
+ pd.testing.assert_series_equal(result["string_col"], mock_df["string_col"])
441
+ pd.testing.assert_series_equal(result["mixed_col"], mock_df["mixed_col"])
442
+ pd.testing.assert_series_equal(result["ones_col"], mock_df["ones_col"])
443
+ pd.testing.assert_series_equal(
444
+ result["squared_col"], mock_df["squared_col"].apply(square)
445
+ )
446
+
447
+
448
+ def test_pluck_entity_data_missing_species_key(sbml_dfs):
449
+ # graph_attrs does not contain 'species' key
450
+ graph_attrs = {}
451
+ result = net_create.pluck_entity_data(sbml_dfs, graph_attrs, SBML_DFS.SPECIES)
452
+ assert result is None
453
+
454
+
455
+ def test_pluck_entity_data_empty_species_dict(sbml_dfs):
456
+ # graph_attrs contains 'species' key but value is empty dict
457
+ graph_attrs = {SBML_DFS.SPECIES: {}}
458
+ result = net_create.pluck_entity_data(sbml_dfs, graph_attrs, SBML_DFS.SPECIES)
459
+ assert result is None
@@ -0,0 +1,30 @@
1
+ import igraph as ig
2
+ import pandas as pd
3
+
4
+ from napistu.network import ng_utils
5
+ from napistu.network.napistu_graph_core import NapistuGraph
6
+
7
+
8
+ def test_napistu_graph_to_pandas_dfs():
9
+ graph_data = [
10
+ (0, 1),
11
+ (0, 2),
12
+ (2, 3),
13
+ (3, 4),
14
+ (4, 2),
15
+ (2, 5),
16
+ (5, 0),
17
+ (6, 3),
18
+ (5, 6),
19
+ ]
20
+
21
+ g = NapistuGraph.from_igraph(ig.Graph(graph_data, directed=True))
22
+ vs, es = ng_utils.napistu_graph_to_pandas_dfs(g)
23
+
24
+ assert all(vs["index"] == list(range(0, 7)))
25
+ assert (
26
+ pd.DataFrame(graph_data)
27
+ .rename({0: "source", 1: "target"}, axis=1)
28
+ .sort_values(["source", "target"])
29
+ .equals(es.sort_values(["source", "target"]))
30
+ )
@@ -0,0 +1,56 @@
1
+ import pandas as pd
2
+
3
+ from napistu.network import paths
4
+ from napistu.network import ng_utils
5
+
6
+
7
+ def test_shortest_paths(sbml_dfs, napistu_graph, napistu_graph_undirected):
8
+ species = sbml_dfs.species
9
+ source_species = species[species["s_name"] == "NADH"]
10
+ dest_species = species[species["s_name"] == "NAD+"]
11
+ target_species_paths = ng_utils.compartmentalize_species_pairs(
12
+ sbml_dfs, source_species.index.tolist(), dest_species.index.tolist()
13
+ )
14
+
15
+ (
16
+ all_shortest_reaction_paths_df,
17
+ _,
18
+ _,
19
+ _,
20
+ ) = paths.find_all_shortest_reaction_paths(
21
+ napistu_graph, sbml_dfs, target_species_paths, weight_var="weights"
22
+ )
23
+
24
+ # undirected graph
25
+ (
26
+ all_shortest_reaction_paths_df,
27
+ all_shortest_reaction_path_edges_df,
28
+ edge_sources,
29
+ paths_graph,
30
+ ) = paths.find_all_shortest_reaction_paths(
31
+ napistu_graph_undirected, sbml_dfs, target_species_paths, weight_var="weights"
32
+ )
33
+
34
+ assert all_shortest_reaction_paths_df.shape[0] == 3
35
+
36
+
37
+ def test_net_polarity():
38
+ polarity_series = pd.Series(
39
+ ["ambiguous", "ambiguous"], index=[0, 1], name="link_polarity"
40
+ )
41
+ assert all(
42
+ [x == "ambiguous" for x in paths._calculate_net_polarity(polarity_series)]
43
+ )
44
+
45
+ polarity_series = pd.Series(
46
+ ["activation", "inhibition", "inhibition", "ambiguous"],
47
+ index=range(0, 4),
48
+ name="link_polarity",
49
+ )
50
+ assert paths._calculate_net_polarity(polarity_series) == [
51
+ "activation",
52
+ "inhibition",
53
+ "activation",
54
+ "ambiguous activation",
55
+ ]
56
+ assert paths._terminal_net_polarity(polarity_series) == "ambiguous activation"
@@ -20,7 +20,7 @@ sbml_model = sbml.SBML(sbml_path).model
20
20
  sbml_dfs = sbml_dfs_core.SBML_dfs(sbml_model)
21
21
  sbml_dfs.validate()
22
22
 
23
- cpr_graph = net_create.process_cpr_graph(
23
+ napistu_graph = net_create.process_napistu_graph(
24
24
  sbml_dfs, graph_type="bipartite", directed=True, weighting_strategy="topology"
25
25
  )
26
26
 
@@ -33,7 +33,7 @@ ORDER = 20
33
33
  TOP_N = 20
34
34
 
35
35
  precomputed_distances = precompute.precompute_distances(
36
- cpr_graph, max_steps=30000, max_score_q=1
36
+ napistu_graph, max_steps=30000, max_score_q=1
37
37
  )
38
38
 
39
39
 
@@ -55,7 +55,9 @@ def test_precomputed_distances_shortest_paths():
55
55
  _,
56
56
  _,
57
57
  _,
58
- ) = paths.find_all_shortest_reaction_paths(cpr_graph, sbml_dfs, all_species_pairs)
58
+ ) = paths.find_all_shortest_reaction_paths(
59
+ napistu_graph, sbml_dfs, all_species_pairs
60
+ )
59
61
 
60
62
  shortest_path_weights = (
61
63
  path_vertices.groupby(["origin", "dest", "path"])["weights"]
@@ -101,7 +103,7 @@ def test_precomputed_distances_shortest_paths():
101
103
 
102
104
  # using the precomputed distances generates the same result as excluding it
103
105
  (precompute_path_vertices, _, _, _) = paths.find_all_shortest_reaction_paths(
104
- cpr_graph,
106
+ napistu_graph,
105
107
  sbml_dfs,
106
108
  all_species_pairs,
107
109
  precomputed_distances=precomputed_distances,
@@ -139,7 +141,7 @@ def test_precomputed_distances_neighborhoods():
139
141
 
140
142
  pruned_neighborhoods_precomputed = neighborhoods.find_and_prune_neighborhoods(
141
143
  sbml_dfs,
142
- cpr_graph,
144
+ napistu_graph,
143
145
  compartmentalized_species,
144
146
  precomputed_distances=precomputed_distances,
145
147
  network_type=NETWORK_TYPE,
@@ -150,7 +152,7 @@ def test_precomputed_distances_neighborhoods():
150
152
 
151
153
  pruned_neighborhoods_otf = neighborhoods.find_and_prune_neighborhoods(
152
154
  sbml_dfs,
153
- cpr_graph,
155
+ napistu_graph,
154
156
  compartmentalized_species,
155
157
  precomputed_distances=None,
156
158
  network_type=NETWORK_TYPE,
@@ -0,0 +1,58 @@
1
+ import pandas as pd
2
+ from napistu.ontologies.genodexito import Genodexito
3
+ from napistu.ontologies.constants import (
4
+ GENODEXITO_DEFS,
5
+ INTERCONVERTIBLE_GENIC_ONTOLOGIES,
6
+ PROTEIN_ONTOLOGIES,
7
+ )
8
+
9
+
10
+ def test_genodexito_mapping_operations():
11
+ """Test Genodexito mapping table creation and operations."""
12
+ # Initialize with test mode and Python method to avoid R dependencies
13
+ geno = Genodexito(
14
+ species="Saccharomyces cerevisiae",
15
+ preferred_method=GENODEXITO_DEFS.PYTHON,
16
+ allow_fallback=False,
17
+ test_mode=True,
18
+ )
19
+
20
+ # Test subset of mappings
21
+ test_mappings = {"ensembl_gene", "symbol", "uniprot"}
22
+
23
+ # Verify test mappings are valid
24
+ assert test_mappings.issubset(
25
+ INTERCONVERTIBLE_GENIC_ONTOLOGIES
26
+ ), "Test mappings must be valid ontologies"
27
+
28
+ # Create mapping tables
29
+ geno.create_mapping_tables(mappings=test_mappings)
30
+
31
+ # Verify mappings were created
32
+ assert geno.mappings is not None, "Mappings should be created"
33
+ assert geno.mapper_used == GENODEXITO_DEFS.PYTHON, "Should use Python mapper"
34
+ assert test_mappings.issubset(
35
+ set(geno.mappings.keys())
36
+ ), "All requested mappings should be present"
37
+
38
+ # Test merge_mappings
39
+ geno.merge_mappings(test_mappings)
40
+ assert isinstance(
41
+ geno.merged_mappings, pd.DataFrame
42
+ ), "merge_mappings should create a DataFrame"
43
+ assert not geno.merged_mappings.empty, "Merged mappings should not be empty"
44
+ assert (
45
+ set(geno.merged_mappings.columns) & test_mappings == test_mappings
46
+ ), "Merged mappings should contain all requested ontologies"
47
+
48
+ # Test stack_mappings with protein ontologies
49
+ protein_test_mappings = set(PROTEIN_ONTOLOGIES) & test_mappings
50
+ if protein_test_mappings: # Only test if we have protein ontologies
51
+ geno.stack_mappings(protein_test_mappings)
52
+ assert isinstance(
53
+ geno.stacked_mappings, pd.DataFrame
54
+ ), "stack_mappings should create a DataFrame"
55
+ assert not geno.stacked_mappings.empty, "Stacked mappings should not be empty"
56
+ assert (
57
+ set(geno.stacked_mappings["ontology"].unique()) == protein_test_mappings
58
+ ), "Stacked mappings should contain all requested protein ontologies"