diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c44809f1..2bd7b4ae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,21 +1,18 @@ Changelog ========= -Version v2.1.0 +Version v3.0.0 -------------- New Features ~~~~~~~~~~~~ - Added simulation config validation -- Added a new commandline subcommand: ``validate-simulation`` -- Added an alias ``validate-circuit`` for the old ``validate`` subcommand - - - deprecated ``validate`` - +- Added a new commandline subcommands: ``validate-simulation``, ``validate-circuit`` Breaking Changes ~~~~~~~~~~~~~~~~ -- Deprecated the commandline subcommand ``validate`` in favor of new ``validate-circuit`` command +- Edge populations' ``iter_connections`` returns ``CircuitNodeId`` instead of ``int`` +- Removed the commandline subcommand ``validate`` in favor of new ``validate-circuit`` command Version v2.0.2 diff --git a/bluepysnap/circuit_validation.py b/bluepysnap/circuit_validation.py index f7af2b07..0040e412 100644 --- a/bluepysnap/circuit_validation.py +++ b/bluepysnap/circuit_validation.py @@ -2,6 +2,7 @@ The idea here is to not depend on libsonata if possible, so we can use this in all situations """ + import logging from pathlib import Path diff --git a/bluepysnap/cli.py b/bluepysnap/cli.py index e77da4ad..669e8172 100644 --- a/bluepysnap/cli.py +++ b/bluepysnap/cli.py @@ -1,12 +1,11 @@ """The project's command line launcher.""" + import functools import logging -import warnings import click from bluepysnap import circuit_validation, simulation_validation -from bluepysnap.utils import Deprecate CLICK_EXISTING_FILE = click.Path(exists=True, file_okay=True, dir_okay=False) @@ -43,27 +42,6 @@ def wrapper(*args, **kwargs): return wrapper -@cli.command() -@circuit_validation_params -def validate(config_file, skip_slow, only_errors): - """[DEPRECATED] Validate Sonata circuit based on config file. - - Args: - config_file (str): path to Sonata circuit config file - skip_slow (bool): skip slow tests - only_errors (bool): only print fatal errors - """ - with warnings.catch_warnings(): - # Making sure the warning is shown - warnings.simplefilter("always", DeprecationWarning) - Deprecate.warn( - "Calling circuit validation with 'validate' is deprecated. " - "Please use 'validate-circuit' instead." - ) - - circuit_validation.validate(config_file, skip_slow, only_errors) - - @cli.command() @circuit_validation_params def validate_circuit(config_file, skip_slow, only_errors): diff --git a/bluepysnap/edges/edge_population.py b/bluepysnap/edges/edge_population.py index 32854b88..db8c675a 100644 --- a/bluepysnap/edges/edge_population.py +++ b/bluepysnap/edges/edge_population.py @@ -26,7 +26,7 @@ from more_itertools import first from bluepysnap import query, utils -from bluepysnap.circuit_ids import CircuitEdgeIds +from bluepysnap.circuit_ids import CircuitEdgeIds, CircuitNodeId from bluepysnap.circuit_ids_types import IDS_DTYPE, CircuitEdgeId from bluepysnap.exceptions import BluepySnapError from bluepysnap.sonata_constants import DYNAMICS_PREFIX, ConstContainer, Edge @@ -518,6 +518,41 @@ def _optimal_direction(): secondary_node_ids_used.add(conn_node_id) break + def _add_circuit_ids(self, its): + """Completes the CircuitNodeId.""" + + return ( + ( + CircuitNodeId(self.source.name, source_id), + CircuitNodeId(self.target.name, target_id), + count, + ) + for source_id, target_id, count in its + ) + + def _add_edge_ids(self, its): + """Completes the CircuitNodeId and adds the CircuitEdgeIds.""" + + return ( + ( + CircuitNodeId(self.source.name, source_id), + CircuitNodeId(self.target.name, target_id), + CircuitEdgeIds.from_dict({self.name: self.pair_edges(source_id, target_id)}), + ) + for source_id, target_id, _ in its + ) + + def _omit_edge_count(self, its): + """Completes the CircuitNodeId and removes the edge count.""" + + return ( + ( + CircuitNodeId(self.source.name, source_id), + CircuitNodeId(self.target.name, target_id), + ) + for source_id, target_id, _ in its + ) + def iter_connections( self, source=None, @@ -557,13 +592,11 @@ def iter_connections( it = self._iter_connections(source_node_ids, target_node_ids, unique_node_ids, shuffle) if return_edge_count: - return it + return self._add_circuit_ids(it) elif return_edge_ids: - add_edge_ids = lambda x: (x[0], x[1], self.pair_edges(x[0], x[1])) - return map(add_edge_ids, it) + return self._add_edge_ids(it) else: - omit_edge_count = lambda x: x[:2] - return map(omit_edge_count, it) + return self._omit_edge_count(it) @property def h5_filepath(self): diff --git a/bluepysnap/edges/edges.py b/bluepysnap/edges/edges.py index 8d3da1bb..1f000942 100644 --- a/bluepysnap/edges/edges.py +++ b/bluepysnap/edges/edges.py @@ -21,7 +21,6 @@ from bluepysnap._doctools import AbstractDocSubstitutionMeta from bluepysnap.circuit_ids import CircuitEdgeIds, CircuitNodeIds -from bluepysnap.circuit_ids_types import CircuitNodeId from bluepysnap.edges.edge_population import EdgePopulation from bluepysnap.exceptions import BluepySnapError from bluepysnap.network import NetworkObject @@ -221,39 +220,6 @@ def pair_edges(self, source_node_id, target_node_id, properties=None): source=source_node_id, target=target_node_id, properties=properties ) - @staticmethod - def _add_circuit_ids(its, source, target): - """Generator comprehension adding the CircuitIds to the iterator. - - Notes: - Using closures or lambda functions would result in override functions and so the - source and target would be the same for all the populations. - """ - return ( - (CircuitNodeId(source, source_id), CircuitNodeId(target, target_id), count) - for source_id, target_id, count in its - ) - - @staticmethod - def _add_edge_ids(its, source, target, pop_name): - """Generator comprehension adding the CircuitIds to the iterator.""" - return ( - ( - CircuitNodeId(source, source_id), - CircuitNodeId(target, target_id), - CircuitEdgeIds.from_dict({pop_name: edge_id}), - ) - for source_id, target_id, edge_id in its - ) - - @staticmethod - def _omit_edge_count(its, source, target): - """Generator comprehension adding the CircuitIds to the iterator.""" - return ( - (CircuitNodeId(source, source_id), CircuitNodeId(target, target_id)) - for source_id, target_id in its - ) - def iter_connections( self, source=None, target=None, return_edge_ids=False, return_edge_count=False ): @@ -276,21 +242,13 @@ def iter_connections( raise BluepySnapError( "`return_edge_count` and `return_edge_ids` are mutually exclusive" ) - for name, pop in self.items(): - it = pop.iter_connections( + for pop in self.values(): + yield from pop.iter_connections( source=source, target=target, return_edge_ids=return_edge_ids, return_edge_count=return_edge_count, ) - source_pop = pop.source.name - target_pop = pop.target.name - if return_edge_count: - yield from self._add_circuit_ids(it, source_pop, target_pop) - elif return_edge_ids: - yield from self._add_edge_ids(it, source_pop, target_pop, name) - else: - yield from self._omit_edge_count(it, source_pop, target_pop) def __getstate__(self): """Make Edges pickle-able, without storing state of caches.""" diff --git a/bluepysnap/query.py b/bluepysnap/query.py index 079b62c5..eed9a3c3 100644 --- a/bluepysnap/query.py +++ b/bluepysnap/query.py @@ -1,4 +1,5 @@ """Module to process search queries of nodes/edges.""" + import operator from collections.abc import Mapping from copy import deepcopy diff --git a/bluepysnap/schemas/__init__.py b/bluepysnap/schemas/__init__.py index 62cb5d4c..0df785bd 100644 --- a/bluepysnap/schemas/__init__.py +++ b/bluepysnap/schemas/__init__.py @@ -1,4 +1,5 @@ """Schemas and schema parser.""" + from bluepysnap.schemas.schemas import ( validate_circuit_schema, validate_edges_schema, diff --git a/bluepysnap/schemas/schemas.py b/bluepysnap/schemas/schemas.py index 222ee90b..1531b6aa 100644 --- a/bluepysnap/schemas/schemas.py +++ b/bluepysnap/schemas/schemas.py @@ -1,4 +1,5 @@ """Functions for schema based validation of circuit files.""" + from pathlib import Path import h5py diff --git a/bluepysnap/simulation_validation.py b/bluepysnap/simulation_validation.py index 6f8b217f..63a23aa8 100644 --- a/bluepysnap/simulation_validation.py +++ b/bluepysnap/simulation_validation.py @@ -1,4 +1,5 @@ """Standalone module that validates Sonata simulation. See ``validate-simulation`` function.""" + import contextlib import io import os diff --git a/tests/data/reporting/create_reports.py b/tests/data/reporting/create_reports.py index 401ec433..761da564 100644 --- a/tests/data/reporting/create_reports.py +++ b/tests/data/reporting/create_reports.py @@ -1,4 +1,5 @@ """Taken from the libsonata lib.""" + import h5py import numpy as np diff --git a/tests/test_cli.py b/tests/test_cli.py index dc939351..5e363262 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -14,36 +14,20 @@ @patch("bluepysnap.schemas.validate_nodes_schema", Mock(return_value=[])) @patch("bluepysnap.schemas.validate_edges_schema", Mock(return_value=[])) @patch("bluepysnap.schemas.validate_circuit_schema", Mock(return_value=[])) -def test_cli_correct(): +def test_cli_validate_circuit_correct(): runner = CliRunner() - - with pytest.warns( - BluepySnapDeprecationWarning, - match="Calling circuit validation with 'validate' is deprecated", - ): - result = runner.invoke(cli, ["validate", str(TEST_DATA_DIR / "circuit_config.json")]) - + result = runner.invoke(cli, ["validate-circuit", str(TEST_DATA_DIR / "circuit_config.json")]) assert result.exit_code == 0 assert click.style("No Error: Success.", fg="green") in result.stdout -def test_cli_no_config(): +def test_cli_validate_circuit_no_config(): runner = CliRunner() - result = runner.invoke(cli, ["validate"]) + result = runner.invoke(cli, ["validate-circuit"]) assert result.exit_code == 2 assert "Missing argument 'CONFIG_FILE'" in result.stdout -@patch("bluepysnap.schemas.validate_nodes_schema", Mock(return_value=[])) -@patch("bluepysnap.schemas.validate_edges_schema", Mock(return_value=[])) -@patch("bluepysnap.schemas.validate_circuit_schema", Mock(return_value=[])) -def test_cli_validate_circuit_correct(): - runner = CliRunner() - result = runner.invoke(cli, ["validate-circuit", str(TEST_DATA_DIR / "circuit_config.json")]) - assert result.exit_code == 0 - assert click.style("No Error: Success.", fg="green") in result.stdout - - def test_cli_validate_simulation_correct(): runner = CliRunner() result = runner.invoke( @@ -51,3 +35,10 @@ def test_cli_validate_simulation_correct(): ) assert result.exit_code == 0 assert click.style("No Error: Success.", fg="green") in result.stdout + + +def test_cli_validate_simulation_no_config(): + runner = CliRunner() + result = runner.invoke(cli, ["validate-simulation"]) + assert result.exit_code == 2 + assert "Missing argument 'CONFIG_FILE'" in result.stdout diff --git a/tests/test_edge_population.py b/tests/test_edge_population.py index 0d110b9b..6b33e5b5 100644 --- a/tests/test_edge_population.py +++ b/tests/test_edge_population.py @@ -13,7 +13,7 @@ from bluepysnap.bbp import Synapse from bluepysnap.circuit import Circuit from bluepysnap.circuit_ids import CircuitEdgeIds, CircuitNodeIds -from bluepysnap.circuit_ids_types import IDS_DTYPE, CircuitEdgeId +from bluepysnap.circuit_ids_types import IDS_DTYPE, CircuitEdgeId, CircuitNodeId from bluepysnap.exceptions import BluepySnapError from bluepysnap.sonata_constants import DEFAULT_EDGE_TYPE, Edge @@ -519,18 +519,29 @@ def test_pathway_edges_6(self): def test_iter_connections_1(self): it = self.test_obj.iter_connections([0, 2], [1]) - assert next(it) == (0, 1) - assert next(it) == (2, 1) + assert next(it) == ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=1), + ) + assert next(it) == ( + CircuitNodeId(population="default", id=2), + CircuitNodeId(population="default", id=1), + ) with pytest.raises(StopIteration): next(it) def test_iter_connections_2(self): it = self.test_obj.iter_connections([0, 2], [1], unique_node_ids=True) - assert list(it) == [(0, 1)] + assert list(it) == [ + (CircuitNodeId(population="default", id=0), CircuitNodeId(population="default", id=1)), + ] def test_iter_connections_3(self): it = self.test_obj.iter_connections([0, 2], [1], shuffle=True) - assert sorted(it) == [(0, 1), (2, 1)] + assert sorted(it) == [ + (CircuitNodeId(population="default", id=0), CircuitNodeId(population="default", id=1)), + (CircuitNodeId(population="default", id=2), CircuitNodeId(population="default", id=1)), + ] def test_iter_connections_4(self): it = self.test_obj.iter_connections(None, None) @@ -539,11 +550,17 @@ def test_iter_connections_4(self): def test_iter_connections_5(self): it = self.test_obj.iter_connections(None, [1]) - assert list(it) == [(0, 1), (2, 1)] + assert list(it) == [ + (CircuitNodeId(population="default", id=0), CircuitNodeId(population="default", id=1)), + (CircuitNodeId(population="default", id=2), CircuitNodeId(population="default", id=1)), + ] def test_iter_connections_6(self): it = self.test_obj.iter_connections([2], None) - assert list(it) == [(2, 0), (2, 1)] + assert list(it) == [ + (CircuitNodeId(population="default", id=2), CircuitNodeId(population="default", id=0)), + (CircuitNodeId(population="default", id=2), CircuitNodeId(population="default", id=1)), + ] def test_iter_connections_7(self): it = self.test_obj.iter_connections([], [0, 1, 2]) @@ -551,11 +568,36 @@ def test_iter_connections_7(self): def test_iter_connections_8(self): it = self.test_obj.iter_connections([0, 2], [1], return_edge_ids=True) - npt.assert_equal(list(it), [(0, 1, [1, 2]), (2, 1, [3])]) + npt.assert_equal( + list(it), + [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=1), + CircuitEdgeIds.from_dict({"default": [1, 2]}), + ), + ( + CircuitNodeId(population="default", id=2), + CircuitNodeId(population="default", id=1), + CircuitEdgeIds.from_dict({"default": [3]}), + ), + ], + ) def test_iter_connections_9(self): it = self.test_obj.iter_connections([0, 2], [1], return_edge_count=True) - assert list(it) == [(0, 1, 2), (2, 1, 1)] + assert list(it) == [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=1), + 2, + ), + ( + CircuitNodeId(population="default", id=2), + CircuitNodeId(population="default", id=1), + 1, + ), + ] def test_iter_connections_10(self): with pytest.raises(BluepySnapError): @@ -577,25 +619,104 @@ def test_iter_connection_unique(self): test_obj = TestEdgePopulation.get_edge_population(config_path, "default") it = test_obj.iter_connections([0, 1, 2], [0, 1, 2]) - assert sorted(it) == [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] + assert sorted(it) == [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=1), + ), + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=2), + ), + ( + CircuitNodeId(population="default", id=1), + CircuitNodeId(population="default", id=0), + ), + ( + CircuitNodeId(population="default", id=1), + CircuitNodeId(population="default", id=2), + ), + ( + CircuitNodeId(population="default", id=2), + CircuitNodeId(population="default", id=0), + ), + ( + CircuitNodeId(population="default", id=2), + CircuitNodeId(population="default", id=1), + ), + ] it = test_obj.iter_connections([0, 1, 2], [0, 1, 2], unique_node_ids=True) - assert sorted(it) == [(0, 1), (1, 0)] + assert sorted(it) == [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=1), + ), + ( + CircuitNodeId(population="default", id=1), + CircuitNodeId(population="default", id=0), + ), + ] it = test_obj.iter_connections([0, 1, 2], [0, 2], unique_node_ids=True) - assert sorted(it) == [(0, 2), (1, 0)] + assert sorted(it) == [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=2), + ), + ( + CircuitNodeId(population="default", id=1), + CircuitNodeId(population="default", id=0), + ), + ] it = test_obj.iter_connections([0, 2], [0, 2], unique_node_ids=True) - assert sorted(it) == [(0, 2), (2, 0)] + assert sorted(it) == [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=2), + ), + ( + CircuitNodeId(population="default", id=2), + CircuitNodeId(population="default", id=0), + ), + ] it = test_obj.iter_connections([0, 1, 2], [0, 2, 1], unique_node_ids=True) - assert sorted(it) == [(0, 1), (1, 0)] + assert sorted(it) == [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=1), + ), + ( + CircuitNodeId(population="default", id=1), + CircuitNodeId(population="default", id=0), + ), + ] it = test_obj.iter_connections([1, 2], [0, 1, 2], unique_node_ids=True) - assert sorted(it) == [(1, 0), (2, 1)] + assert sorted(it) == [ + ( + CircuitNodeId(population="default", id=1), + CircuitNodeId(population="default", id=0), + ), + ( + CircuitNodeId(population="default", id=2), + CircuitNodeId(population="default", id=1), + ), + ] it = test_obj.iter_connections([0, 1, 2], [1, 2], unique_node_ids=True) - assert sorted(it) == [(0, 1), (1, 2)] + assert sorted(it) == [ + ( + CircuitNodeId(population="default", id=0), + CircuitNodeId(population="default", id=1), + ), + ( + CircuitNodeId(population="default", id=1), + CircuitNodeId(population="default", id=2), + ), + ] def test_h5_filepath_from_config(self): assert self.test_obj.h5_filepath == str(TEST_DATA_DIR / "edges.h5") diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 0bbc393b..47e7c7bd 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -187,8 +187,8 @@ def test_validate_config_ok_missing_optional_fields(to_remove): [["networks", "edges", 0, "populations"]], "networks.edges[0]: 'populations' is a required property", ), - ([["networks", "nodes", 0]], "networks.nodes: [] is too short"), - ([["networks", "edges", 0]], "networks.edges: [] is too short"), + ([["networks", "nodes", 0]], "networks.nodes: [] should be non-empty"), + ([["networks", "edges", 0]], "networks.edges: [] should be non-empty"), ([["networks", "nodes"]], "networks: 'nodes' is a required property"), ([["networks", "edges"]], "networks: 'edges' is a required property"), ([["networks"]], "'networks' is a required property"),