Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions tests/test_comprehensive_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,23 @@ def test_all_negative_regulators_present(self, graph, pathway_id):

@pytest.mark.parametrize("pathway_id", AVAILABLE_PATHWAYS)
def test_negative_regulators_marked_neg(self, graph, pathway_id):
"""All regulator edges with pos_neg='neg' should only be negative regulators."""
"""Negative edges are only the two edge types that may be negative.

`regulator` is the curated NegativeRegulation. `depletion` is the
synthetic catalyst->substrate consumption edge, which is negative by
construction and postdates this test — the solver routes it separately
and applies divide-form inhibition to it (see `depletion_uuids` in
reaction_model.jl). Nothing else may carry pos_neg='neg'.
"""
pathway_dir = find_pathway_dir(pathway_id)
network = pd.read_csv(pathway_dir / "logic_network.csv")

negative_edge_types = {"regulator", "depletion"}
neg_edges = network[network['pos_neg'] == 'neg']
# All negative edges should be regulators (not catalysts or main edges)
for _, edge in neg_edges.iterrows():
assert edge['edge_type'] == 'regulator', (
f"Found neg edge with edge_type='{edge['edge_type']}' instead of 'regulator'"
assert edge['edge_type'] in negative_edge_types, (
f"Found neg edge with edge_type='{edge['edge_type']}'; "
f"only {sorted(negative_edge_types)} may be negative"
)


Expand Down
34 changes: 30 additions & 4 deletions tests/test_network_invariants.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,41 @@ def test_valid_pos_neg_values(self, network):
def test_and_logic_consistency(self, network):
"""AND ⇔ contributes to the reaction proceeding.

Allowed: input, catalyst, positive regulator.
Disallowed: output, negative regulator (any one blocker suffices,
so neg regulators are OR).
Allowed: input, catalyst, positive regulator, assembly, dissociation,
depletion. Disallowed: output, negative regulator (any one blocker
suffices, so neg regulators are OR).

The allowlist originally covered only input/catalyst/positive-regulator
and so failed on every pathway in the catalog — 149k edges across all
92 — which is why it went unread. Each addition below was checked
against what the solver actually does with `and_or`:

- `assembly` (61,652 edges): AND is correct — a Complex IS the AND of
its subunits. It is also inert under the default config, because
`DS_ASSEMBLY_LIMITING` routes assembly inputs to a limiting-reactant
(min) rule before the AND/OR branch is reached
(reaction_model.jl, `activator_is_assembly`).
- `depletion` (5,064 edges): inert. `create_reaction_from_edges` pushes
depletion edges to their own vector and never records `is_and`.
- `dissociation` (82,394 edges): live, and inconsistent with every
other producer edge — all 116k `output` edges are `or`, these are
`and`. They feed terminal readout sinks with exactly one producer
each, so the question is only whether a lone parent gets the
`hill_log` tanh (AND) or passes through (OR). Measured: hill_log is
identity to four decimals at both classification cutoffs (0.85 and
1.15) and within 0.1% out to fold 2, only compressing large folds
(100 -> 74). Flipping them to `or` and re-benchmarking moved nothing
real — all 11 changed predictions were in one pathway and none had
both arms converged. Inconsistent but immaterial; left as-is rather
than changed without evidence.
"""
and_edges = network[network['and_or'] == 'and']
if len(and_edges) == 0:
pytest.skip("No AND edges")
allowed = (
and_edges['edge_type'].isin({'input', 'catalyst'})
and_edges['edge_type'].isin(
{'input', 'catalyst', 'assembly', 'dissociation', 'depletion'}
)
| (
(and_edges['edge_type'] == 'regulator')
& (and_edges['pos_neg'] == 'pos')
Expand Down
Loading