Skip to content
Open
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
79 changes: 65 additions & 14 deletions src/test/test_downstream_class_connectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ def test_row_has_expected_keys(self):
assert result["rows"], "Expected at least one row"
row = result["rows"][0]
expected_keys = {
"id", "downstream_class", "total_n", "connected_n",
"percent_connected", "pairwise_connections", "total_weight", "avg_weight",
"id", "query_id", "upstream_class", "downstream_class",
"total_n", "connected_n", "percent_connected",
"pairwise_connections", "total_weight", "avg_weight",
}
assert expected_keys.issubset(row.keys())

Expand Down Expand Up @@ -87,8 +88,9 @@ def test_dataframe_has_expected_columns(self):
TEST_CLASS, return_dataframe=True, limit=1, force_refresh=True
)
expected_cols = {
"id", "downstream_class", "total_n", "connected_n",
"percent_connected", "pairwise_connections", "total_weight", "avg_weight",
"id", "query_id", "upstream_class", "downstream_class",
"total_n", "connected_n", "percent_connected",
"pairwise_connections", "total_weight", "avg_weight",
}
assert expected_cols.issubset(set(df.columns))

Expand Down Expand Up @@ -129,7 +131,7 @@ def test_parent_class_appears_with_sensible_counts(self, result):
"""
from vfbquery.vfb_queries import vc, get_dict_cursor

rows = result["rows"]
rows = [r for r in result["rows"] if r["query_id"] == TEST_CLASS]
ids = [r["id"] for r in rows]
assert ids, "Expected at least one row to test against"

Expand All @@ -147,7 +149,7 @@ def test_parent_class_appears_with_sensible_counts(self, result):
parent_id = pairs[0]["parent"]
child_id = pairs[0]["child"]
parent_row = next(r for r in rows if r["id"] == parent_id)
# Sum connected_n across all descendant rows (not just the one returned).
# Sum connected_n across all descendant rows.
desc_q = (
"MATCH (p:Class {short_form: '%s'})<-[:SUBCLASSOF*1..]-(c:Class) "
"WHERE c.short_form IN %s "
Expand All @@ -169,18 +171,67 @@ def test_parent_class_appears_with_sensible_counts(self, result):
)

@pytest.mark.integration
def test_total_n_is_constant_across_rows(self, result):
"""`total_n` is the queried-side instance count and must be the same
for every output row (regression for the previous summed-across-
subclasses value).
def test_total_n_constant_within_each_query_class(self, result):
"""In the downstream direction the presynaptic side is the queried
class, so (matching VFB_connect's normalization) `total_n` is the
queried (sub)class instance count: constant within each query block (it
varies between blocks), and `connected_n` never exceeds it.
"""
from collections import defaultdict

rows = result["rows"]
assert rows, "Expected at least one row"
total_ns = {r["total_n"] for r in rows}
assert len(total_ns) == 1, (
f"Expected total_n to be constant across rows, got: {total_ns}"
by_query = defaultdict(set)
for r in rows:
assert r["connected_n"] <= r["total_n"], (
f"connected_n={r['connected_n']} > total_n={r['total_n']} "
f"for {r['id']}"
)
by_query[r["query_id"]].add(r["total_n"])
for qid, totals in by_query.items():
assert len(totals) == 1, (
f"Expected total_n constant within block {qid}, got: {totals}"
)
assert next(iter(totals)) > 0

@pytest.mark.integration
def test_includes_subclass_breakdown(self, result):
"""The result should contain the input term's own rows plus a block of
rows for each subclass that has connectivity instances. Any non-input
query_id must be a genuine subclass of the input term.
"""
from vfbquery.vfb_queries import vc, get_dict_cursor

rows = result["rows"]
query_ids = {r["query_id"] for r in rows}
assert TEST_CLASS in query_ids, "Expected the input term's own rows"

# Full subclass closure (incl. the input term itself).
q = (
"MATCH (sub:Class)-[:SUBCLASSOF*0..]->(:Class {short_form: '%s'}) "
"RETURN collect(DISTINCT sub.short_form) AS ids" % TEST_CLASS
)
subtree_rows = get_dict_cursor()(vc.nc.commit_list([q]))
subtree = set(subtree_rows[0]["ids"]) if subtree_rows else set()
offenders = [q for q in query_ids if q not in subtree]
assert not offenders, (
f"query_id(s) not in the input term's subclass closure: {offenders}"
)

# Subclasses of the input term that have connectivity instances.
sub_q = (
"MATCH (sub:Class)-[:SUBCLASSOF*1..]->(:Class {short_form: '%s'}) "
"WHERE (sub)<-[:SUBCLASSOF*0..]-(:Class)<-[:INSTANCEOF]-"
"(:Individual:has_neuron_connectivity) "
"RETURN collect(DISTINCT sub.short_form) AS ids" % TEST_CLASS
)
sub_rows = get_dict_cursor()(vc.nc.commit_list([sub_q]))
connected_subclasses = set(sub_rows[0]["ids"]) if sub_rows else set()
if not connected_subclasses:
pytest.skip("Input term has no connectivity-bearing subclasses")
assert query_ids & connected_subclasses, (
"Expected subclass breakdown rows but none were present"
)
assert next(iter(total_ns)) > 0

@pytest.mark.integration
def test_no_rows_above_neuron_root(self, result):
Expand Down
80 changes: 68 additions & 12 deletions src/test/test_upstream_class_connectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ def test_row_has_expected_keys(self):
assert result["rows"], "Expected at least one row"
row = result["rows"][0]
expected_keys = {
"id", "upstream_class", "total_n", "connected_n",
"percent_connected", "pairwise_connections", "total_weight", "avg_weight",
"id", "query_id", "upstream_class", "downstream_class",
"total_n", "connected_n", "percent_connected",
"pairwise_connections", "total_weight", "avg_weight",
}
assert expected_keys.issubset(row.keys())

Expand Down Expand Up @@ -87,8 +88,9 @@ def test_dataframe_has_expected_columns(self):
TEST_CLASS, return_dataframe=True, limit=1, force_refresh=True
)
expected_cols = {
"id", "upstream_class", "total_n", "connected_n",
"percent_connected", "pairwise_connections", "total_weight", "avg_weight",
"id", "query_id", "upstream_class", "downstream_class",
"total_n", "connected_n", "percent_connected",
"pairwise_connections", "total_weight", "avg_weight",
}
assert expected_cols.issubset(set(df.columns))

Expand Down Expand Up @@ -126,10 +128,13 @@ def test_parent_class_appears_with_sensible_counts(self, result):
"""A row keyed on a parent class should have connected_n at least as
large as any of its descendant rows (set-union semantics) and at most
the sum of descendant connected_n.

Restricted to the input term's own block so partner rows are not mixed
across queried (sub)classes.
"""
from vfbquery.vfb_queries import vc, get_dict_cursor

rows = result["rows"]
rows = [r for r in result["rows"] if r["query_id"] == TEST_CLASS]
ids = [r["id"] for r in rows]
assert ids, "Expected at least one row to test against"

Expand Down Expand Up @@ -166,17 +171,68 @@ def test_parent_class_appears_with_sensible_counts(self, result):
)

@pytest.mark.integration
def test_total_n_is_constant_across_rows(self, result):
"""`total_n` is the queried-side instance count and must be the same
for every output row.
def test_total_n_is_per_partner(self, result):
"""In the upstream direction the presynaptic side is the partner, so
(matching VFB_connect's normalization) `total_n` describes the partner
(`upstream_class`): it must be constant across every row referencing the
same partner id, regardless of which queried (sub)class block it is in,
and `connected_n` must never exceed it.
"""
from collections import defaultdict

rows = result["rows"]
assert rows, "Expected at least one row"
total_ns = {r["total_n"] for r in rows}
assert len(total_ns) == 1, (
f"Expected total_n to be constant across rows, got: {total_ns}"
by_partner = defaultdict(set)
for r in rows:
assert r["connected_n"] <= r["total_n"], (
f"connected_n={r['connected_n']} > total_n={r['total_n']} "
f"for {r['id']}"
)
by_partner[r["id"]].add(r["total_n"])
for pid, totals in by_partner.items():
assert len(totals) == 1, (
f"total_n varies for partner {pid}: {totals}"
)
assert next(iter(totals)) > 0

@pytest.mark.integration
def test_includes_subclass_breakdown(self, result):
"""The result should contain the input term's own rows plus a block of
rows for each subclass that has connectivity instances. Any non-input
query_id must be a genuine subclass of the input term.
"""
from vfbquery.vfb_queries import vc, get_dict_cursor

rows = result["rows"]
query_ids = {r["query_id"] for r in rows}
assert TEST_CLASS in query_ids, "Expected the input term's own rows"

# Full subclass closure (incl. the input term itself).
q = (
"MATCH (sub:Class)-[:SUBCLASSOF*0..]->(:Class {short_form: '%s'}) "
"RETURN collect(DISTINCT sub.short_form) AS ids" % TEST_CLASS
)
subtree_rows = get_dict_cursor()(vc.nc.commit_list([q]))
subtree = set(subtree_rows[0]["ids"]) if subtree_rows else set()
offenders = [q for q in query_ids if q not in subtree]
assert not offenders, (
f"query_id(s) not in the input term's subclass closure: {offenders}"
)

# Subclasses of the input term that have connectivity instances.
sub_q = (
"MATCH (sub:Class)-[:SUBCLASSOF*1..]->(:Class {short_form: '%s'}) "
"WHERE (sub)<-[:SUBCLASSOF*0..]-(:Class)<-[:INSTANCEOF]-"
"(:Individual:has_neuron_connectivity) "
"RETURN collect(DISTINCT sub.short_form) AS ids" % TEST_CLASS
)
sub_rows = get_dict_cursor()(vc.nc.commit_list([sub_q]))
connected_subclasses = set(sub_rows[0]["ids"]) if sub_rows else set()
if not connected_subclasses:
pytest.skip("Input term has no connectivity-bearing subclasses")
assert query_ids & connected_subclasses, (
"Expected subclass breakdown rows but none were present"
)
assert next(iter(total_ns)) > 0

@pytest.mark.integration
def test_no_rows_above_neuron_root(self, result):
Expand Down
Loading
Loading