BlueBrain / snap

@@ -34,125 +34,6 @@
Loading
34 34
from bluepysnap.sonata_constants import DYNAMICS_PREFIX, ConstContainer, Node
35 35
36 36
37 -
class Nodes(
38 -
    NetworkObject,
39 -
    metaclass=AbstractDocSubstitutionMeta,
40 -
    source_word="NetworkObject",
41 -
    target_word="Node",
42 -
):
43 -
    """The top level Nodes accessor."""
44 -
45 -
    def __init__(self, circuit):  # pylint: disable=useless-super-delegation
46 -
        """Initialize the top level Nodes accessor."""
47 -
        super().__init__(circuit)
48 -
49 -
    @property
50 -
    def _population_class(self):
51 -
        return NodePopulation
52 -
53 -
    @cached_property
54 -
    def population_names(self):
55 -
        """Defines all sorted node population names from the Circuit."""
56 -
        return sorted(self._circuit.to_libsonata.node_populations)
57 -
58 -
    def property_values(self, prop):
59 -
        """Returns all the values for a given Nodes property."""
60 -
        return set(
61 -
            value
62 -
            for pop in self.values()
63 -
            if prop in pop.property_names
64 -
            for value in pop.property_values(prop)
65 -
        )
66 -
67 -
    def ids(self, group=None, sample=None, limit=None):
68 -
        """Returns the CircuitNodeIds corresponding to the nodes from ``group``.
69 -
70 -
        Args:
71 -
            group (CircuitNodeId/CircuitNodeIds/int/sequence/str/mapping/None): Which IDs will be
72 -
            returned depends on the type of the ``group`` argument:
73 -
                - ``CircuitNodeId``: return the ID in a CircuitNodeIds object if it belongs to
74 -
                    the circuit.
75 -
                - ``CircuitNodeIds``: return the IDs in a CircuitNodeIds object if they belong to
76 -
                    the circuit.
77 -
                - ``int``: if the node ID is present in all populations, returns a CircuitNodeIds
78 -
                    object containing the corresponding node ID for all populations.
79 -
                - ``sequence``: if all the values contained in the sequence are present in all
80 -
                    populations, returns a CircuitNodeIds object containing the corresponding node
81 -
                    IDs for all populations.
82 -
                - ``str``: use a node set name as input. Returns a CircuitNodeIds object containing
83 -
                    nodes selected by the node set.
84 -
                - ``mapping``: Returns a CircuitNodeIds object containing nodes matching a
85 -
                    properties filter.
86 -
                - ``None``: return all node IDs of the circuit in a CircuitNodeIds object.
87 -
            sample (int): If specified, randomly choose ``sample`` number of
88 -
                IDs from the match result. If the size of the sample is greater than
89 -
                the size of all the NodePopulations then all ids are taken and shuffled.
90 -
            limit (int): If specified, return the first ``limit`` number of
91 -
                IDs from the match result. If limit is greater than the size of all the populations,
92 -
                all node IDs are returned.
93 -
94 -
        Returns:
95 -
            CircuitNodeIds: returns a CircuitNodeIds containing all the node IDs and the
96 -
                corresponding populations. All the explicitly requested IDs must be present inside
97 -
                the circuit.
98 -
99 -
        Raises:
100 -
            BluepySnapError: when a population from a CircuitNodeIds is not present in the circuit.
101 -
            BluepySnapError: when an id query via a int, sequence, or CircuitNodeIds is not present
102 -
                in the circuit.
103 -
104 -
        Examples:
105 -
            The available group parameter values (example with 2 node populations pop1 and pop2):
106 -
107 -
            >>> nodes = circuit.nodes
108 -
            >>> nodes.ids(group=None)  #  returns all CircuitNodeIds from the circuit
109 -
            >>> node_ids = CircuitNodeIds.from_arrays(["pop1", "pop2"], [1, 3])
110 -
            >>> nodes.ids(group=node_ids)  #  returns ID 1 from pop1 and ID 3 from pop2
111 -
            >>> nodes.ids(group=0)  #  returns CircuitNodeIds 0 from pop1 and pop2
112 -
            >>> nodes.ids(group=[0, 1])  #  returns CircuitNodeIds 0 and 1 from pop1 and pop2
113 -
            >>> nodes.ids(group="node_set_name")  # returns CircuitNodeIds matching node set
114 -
            >>> nodes.ids(group={Node.LAYER: 2})  # returns CircuitNodeIds matching layer==2
115 -
            >>> nodes.ids(group={Node.LAYER: [2, 3]})  # returns CircuitNodeIds with layer in [2,3]
116 -
            >>> nodes.ids(group={Node.X: (0, 1)})  # returns CircuitNodeIds with 0 < x < 1
117 -
            >>> # returns CircuitNodeIds matching one of the queries inside the 'or' list
118 -
            >>> nodes.ids(group={'$or': [{ Node.LAYER: [2, 3]},
119 -
            >>>                          { Node.X: (0, 1), Node.MTYPE: 'L1_SLAC' }]})
120 -
            >>> # returns CircuitNodeIds matching all the queries inside the 'and' list
121 -
            >>> nodes.ids(group={'$and': [{ Node.LAYER: [2, 3]},
122 -
            >>>                           { Node.X: (0, 1), Node.MTYPE: 'L1_SLAC' }]})
123 -
        """
124 -
        if isinstance(group, CircuitNodeIds):
125 -
            diff = np.setdiff1d(group.get_populations(unique=True), self.population_names)
126 -
            if diff.size != 0:
127 -
                raise BluepySnapError(f"Population {diff} does not exist in the circuit.")
128 -
129 -
        fun = lambda x: (x.ids(group, raise_missing_property=False), x.name)
130 -
        return self._get_ids_from_pop(fun, CircuitNodeIds, sample=sample, limit=limit)
131 -
132 -
    def get(self, group=None, properties=None):  # pylint: disable=arguments-differ
133 -
        """Node properties as a pandas DataFrame.
134 -
135 -
        Args:
136 -
            group (CircuitNodeIds/int/sequence/str/mapping/None): Which nodes will have their
137 -
                properties returned depends on the type of the ``group`` argument:
138 -
                See :py:class:`~bluepysnap.nodes.Nodes.ids`.
139 -
140 -
            properties (str/list): If specified, return only the properties in the list.
141 -
                Otherwise return all properties.
142 -
143 -
        Returns:
144 -
            pandas.DataFrame: Return a pandas DataFrame indexed by NodeCircuitIds containing the
145 -
                properties from ``properties``.
146 -
147 -
        Notes:
148 -
            The NodePopulation.property_names function will give you all the usable properties
149 -
            for the `properties` argument.
150 -
        """
151 -
        if properties is None:
152 -
            properties = self.property_names
153 -
        return super().get(group, properties)
154 -
155 -
156 37
class NodePopulation:
157 38
    """Node population access."""
158 39
@@ -199,11 +80,11 @@
Loading
199 80
            result[attr] = nodes.get_dynamics_attribute(attr.split(DYNAMICS_PREFIX)[1], _all)
200 81
        return result
201 82
202 -
    @cached_property
83 +
    @property
203 84
    def _properties(self):
204 85
        return self._circuit.to_libsonata.node_population_properties(self.name)
205 86
206 -
    @cached_property
87 +
    @property
207 88
    def _population(self):
208 89
        return self._circuit.to_libsonata.node_population(self.name)
209 90
@@ -691,9 +572,129 @@
Loading
691 572
    @cached_property
692 573
    def h5_filepath(self):
693 574
        """Get the H5 nodes file associated with population."""
575 +
        for node_conf in self._circuit.config["networks"]["nodes"]:
576 +
            if self.name in node_conf["populations"]:
577 +
                return node_conf["nodes_file"]
694 578
        for node_conf in self._circuit.config["networks"]["nodes"]:
695 579
            h5_filepath = node_conf["nodes_file"]
696 580
            storage = libsonata.NodeStorage(h5_filepath)
697 581
            if self.name in storage.population_names:  # pylint: disable=unsupported-membership-test
698 582
                return h5_filepath
699 583
        raise BluepySnapError(f"h5_filepath not found for population '{self.name}'")
584 +
585 +
586 +
class Nodes(
587 +
    NetworkObject,
588 +
    metaclass=AbstractDocSubstitutionMeta,
589 +
    source_word="NetworkObject",
590 +
    target_word="Node",
591 +
):
592 +
    """The top level Nodes accessor."""
593 +
594 +
    _population_class = NodePopulation
595 +
596 +
    def __init__(self, circuit):  # pylint: disable=useless-super-delegation
597 +
        """Initialize the top level Nodes accessor."""
598 +
        super().__init__(circuit)
599 +
600 +
    @cached_property
601 +
    def population_names(self):
602 +
        """Defines all sorted node population names from the Circuit."""
603 +
        return sorted(self._circuit.to_libsonata.node_populations)
604 +
605 +
    def property_values(self, prop):
606 +
        """Returns all the values for a given Nodes property."""
607 +
        return set(
608 +
            value
609 +
            for pop in self.values()
610 +
            if prop in pop.property_names
611 +
            for value in pop.property_values(prop)
612 +
        )
613 +
614 +
    def ids(self, group=None, sample=None, limit=None):
615 +
        """Returns the CircuitNodeIds corresponding to the nodes from ``group``.
616 +
617 +
        Args:
618 +
            group (CircuitNodeId/CircuitNodeIds/int/sequence/str/mapping/None): Which IDs will be
619 +
            returned depends on the type of the ``group`` argument:
620 +
                - ``CircuitNodeId``: return the ID in a CircuitNodeIds object if it belongs to
621 +
                    the circuit.
622 +
                - ``CircuitNodeIds``: return the IDs in a CircuitNodeIds object if they belong to
623 +
                    the circuit.
624 +
                - ``int``: if the node ID is present in all populations, returns a CircuitNodeIds
625 +
                    object containing the corresponding node ID for all populations.
626 +
                - ``sequence``: if all the values contained in the sequence are present in all
627 +
                    populations, returns a CircuitNodeIds object containing the corresponding node
628 +
                    IDs for all populations.
629 +
                - ``str``: use a node set name as input. Returns a CircuitNodeIds object containing
630 +
                    nodes selected by the node set.
631 +
                - ``mapping``: Returns a CircuitNodeIds object containing nodes matching a
632 +
                    properties filter.
633 +
                - ``None``: return all node IDs of the circuit in a CircuitNodeIds object.
634 +
            sample (int): If specified, randomly choose ``sample`` number of
635 +
                IDs from the match result. If the size of the sample is greater than
636 +
                the size of all the NodePopulations then all ids are taken and shuffled.
637 +
            limit (int): If specified, return the first ``limit`` number of
638 +
                IDs from the match result. If limit is greater than the size of all the populations,
639 +
                all node IDs are returned.
640 +
641 +
        Returns:
642 +
            CircuitNodeIds: returns a CircuitNodeIds containing all the node IDs and the
643 +
                corresponding populations. All the explicitly requested IDs must be present inside
644 +
                the circuit.
645 +
646 +
        Raises:
647 +
            BluepySnapError: when a population from a CircuitNodeIds is not present in the circuit.
648 +
            BluepySnapError: when an id query via a int, sequence, or CircuitNodeIds is not present
649 +
                in the circuit.
650 +
651 +
        Examples:
652 +
            The available group parameter values (example with 2 node populations pop1 and pop2):
653 +
654 +
            >>> nodes = circuit.nodes
655 +
            >>> nodes.ids(group=None)  #  returns all CircuitNodeIds from the circuit
656 +
            >>> node_ids = CircuitNodeIds.from_arrays(["pop1", "pop2"], [1, 3])
657 +
            >>> nodes.ids(group=node_ids)  #  returns ID 1 from pop1 and ID 3 from pop2
658 +
            >>> nodes.ids(group=0)  #  returns CircuitNodeIds 0 from pop1 and pop2
659 +
            >>> nodes.ids(group=[0, 1])  #  returns CircuitNodeIds 0 and 1 from pop1 and pop2
660 +
            >>> nodes.ids(group="node_set_name")  # returns CircuitNodeIds matching node set
661 +
            >>> nodes.ids(group={Node.LAYER: 2})  # returns CircuitNodeIds matching layer==2
662 +
            >>> nodes.ids(group={Node.LAYER: [2, 3]})  # returns CircuitNodeIds with layer in [2,3]
663 +
            >>> nodes.ids(group={Node.X: (0, 1)})  # returns CircuitNodeIds with 0 < x < 1
664 +
            >>> # returns CircuitNodeIds matching one of the queries inside the 'or' list
665 +
            >>> nodes.ids(group={'$or': [{ Node.LAYER: [2, 3]},
666 +
            >>>                          { Node.X: (0, 1), Node.MTYPE: 'L1_SLAC' }]})
667 +
            >>> # returns CircuitNodeIds matching all the queries inside the 'and' list
668 +
            >>> nodes.ids(group={'$and': [{ Node.LAYER: [2, 3]},
669 +
            >>>                           { Node.X: (0, 1), Node.MTYPE: 'L1_SLAC' }]})
670 +
        """
671 +
        if isinstance(group, CircuitNodeIds):
672 +
            diff = np.setdiff1d(group.get_populations(unique=True), self.population_names)
673 +
            if diff.size != 0:
674 +
                raise BluepySnapError(f"Population {diff} does not exist in the circuit.")
675 +
676 +
        fun = lambda x: (x.ids(group, raise_missing_property=False), x.name)
677 +
        return self._get_ids_from_pop(fun, CircuitNodeIds, sample=sample, limit=limit)
678 +
679 +
    def get(self, group=None, properties=None):  # pylint: disable=arguments-differ
680 +
        """Node properties as a pandas DataFrame.
681 +
682 +
        Args:
683 +
            group (CircuitNodeIds/int/sequence/str/mapping/None): Which nodes will have their
684 +
                properties returned depends on the type of the ``group`` argument:
685 +
                See :py:class:`~bluepysnap.nodes.Nodes.ids`.
686 +
687 +
            properties (str/list): If specified, return only the properties in the list.
688 +
                Otherwise return all properties.
689 +
690 +
        Returns:
691 +
            pandas.DataFrame: Return a pandas DataFrame indexed by NodeCircuitIds containing the
692 +
                properties from ``properties``.
693 +
694 +
        Notes:
695 +
            The NodePopulation.property_names function will give you all the usable properties
696 +
            for the `properties` argument.
697 +
        """
698 +
        if properties is None:
699 +
            properties = self.property_names
700 +
        return super().get(group, properties)

@@ -28,6 +28,8 @@
Loading
28 28
class NetworkObject(abc.ABC):
29 29
    """Abstract class for the top level NetworkObjects accessor."""
30 30
31 +
    _population_class = None
32 +
31 33
    def __init__(self, circuit):
32 34
        """Initialize the top level NetworkObjects accessor."""
33 35
        self._circuit = circuit
@@ -36,11 +38,6 @@
Loading
36 38
        """Collects the different NetworkObjectPopulation and returns them as a dict."""
37 39
        return {name: cls(self._circuit, name) for name in self.population_names}
38 40
39 -
    @property
40 -
    @abc.abstractmethod
41 -
    def _population_class(self):
42 -
        """Should define the NetworkObject population class."""
43 -
44 41
    @cached_property
45 42
    def _populations(self):
46 43
        """Cached population dictionary."""

@@ -34,176 +34,380 @@
Loading
34 34
from bluepysnap.utils import IDS_DTYPE, Deprecate
35 35
36 36
37 -
class Edges(
38 -
    NetworkObject,
39 -
    metaclass=AbstractDocSubstitutionMeta,
40 -
    source_word="NetworkObject",
41 -
    target_word="Edge",
42 -
):
43 -
    """The top level Edges accessor."""
37 +
def _is_empty(xs):
38 +
    return (xs is not None) and (len(xs) == 0)
44 39
45 -
    def __init__(self, circuit):  # pylint: disable=useless-super-delegation
46 -
        """Initialize the top level Edges accessor."""
47 -
        super().__init__(circuit)
48 40
49 -
    @property
50 -
    def _population_class(self):
51 -
        return EdgePopulation
41 +
def _estimate_range_size(func, node_ids, n=3):
42 +
    """Median size of index second level for some node IDs from the provided list."""
43 +
    assert len(node_ids) > 0
44 +
    if len(node_ids) > n:
45 +
        node_ids = np.random.choice(node_ids, size=n, replace=False)
46 +
    return np.median([len(func(node_id).ranges) for node_id in node_ids])
52 47
53 -
    @cached_property
54 -
    def population_names(self):
55 -
        """Defines all sorted edge population names from the Circuit."""
56 -
        return sorted(self._circuit.to_libsonata.edge_populations)
57 48
58 -
    def ids(self, group=None, sample=None, limit=None):
59 -
        """Edge CircuitEdgeIds corresponding to edges ``edge_ids``.
49 +
class EdgePopulation:
50 +
    """Edge population access."""
51 +
52 +
    def __init__(self, circuit, population_name):
53 +
        """Initializes a EdgePopulation object from a EdgeStorage and a population name.
60 54
61 55
        Args:
62 -
            group (None/int/CircuitEdgeId/CircuitEdgeIds/sequence): Which IDs will be
63 -
            returned depends on the type of the ``group`` argument:
64 -
                - ``None``: return all CircuitEdgeIds.
65 -
                - ``CircuitEdgeId``: return the ID in a CircuitEdgeIds object.
66 -
                - ``CircuitEdgeIds``: return the IDs in a CircuitNodeIds object.
67 -
                - ``int``: returns a CircuitEdgeIds object containing the corresponding edge ID
68 -
                    for all populations.
69 -
                - ``sequence``: returns a CircuitEdgeIds object containing the corresponding edge
70 -
                    IDs for all populations.
71 -
            sample (int): If specified, randomly choose ``sample`` number of
72 -
                IDs from the match result. If the size of the sample is greater than
73 -
                the size of all the EdgePopulations then all ids are taken and shuffled.
74 -
            limit (int): If specified, return the first ``limit`` number of
75 -
                IDs from the match result. If limit is greater than the size of all the populations
76 -
                all node IDs are returned.
56 +
            circuit (bluepysnap.Circuit): the circuit object containing the edge population
57 +
            population_name (str): the name of the edge population
77 58
78 59
        Returns:
79 -
            CircuitEdgeIds: returns a CircuitEdgeIds containing all the edge IDs and the
80 -
                corresponding populations. For performance reasons we do not test if the edge ids
81 -
                are present or not in the circuit.
82 -
83 -
        Notes:
84 -
            This envision also the maybe future selection of edges on queries.
60 +
            EdgePopulation: An EdgePopulation object.
85 61
        """
86 -
        if isinstance(group, CircuitEdgeIds):
87 -
            diff = np.setdiff1d(group.get_populations(unique=True), self.population_names)
88 -
            if diff.size != 0:
89 -
                raise BluepySnapError(f"Population {diff} does not exist in the circuit.")
90 -
        fun = lambda x: (x.ids(group), x.name)
91 -
        return self._get_ids_from_pop(fun, CircuitEdgeIds, sample=sample, limit=limit)
62 +
        self._circuit = circuit
63 +
        self.name = population_name
92 64
93 -
    def get(self, edge_ids=None, properties=None):  # pylint: disable=arguments-renamed
94 -
        """Edge properties as pandas DataFrame.
65 +
    @property
66 +
    def _properties(self):
67 +
        return self._circuit.to_libsonata.edge_population_properties(self.name)
95 68
96 -
        Args:
97 -
            edge_ids (int/CircuitEdgeId/CircuitEdgeIds/sequence): same as Edges.ids().
98 -
            properties (None/str/list): an edge property name or a list of edge property names.
99 -
                If set to None ids are returned.
69 +
    @property
70 +
    def _population(self):
71 +
        return self._circuit.to_libsonata.edge_population(self.name)
100 72
101 -
        Returns:
102 -
            pandas.Series/pandas.DataFrame:
103 -
                A pandas Series indexed by edge IDs if ``properties`` is scalar.
104 -
                A pandas DataFrame indexed by edge IDs if ``properties`` is list.
73 +
    @staticmethod
74 +
    def _resolve_node_ids(nodes, group):
75 +
        """Node IDs corresponding to node group filter."""
76 +
        if group is None:
77 +
            return None
78 +
        return nodes.ids(group)
105 79
106 -
        Notes:
107 -
            The Edges.property_names function will give you all the usable properties
108 -
            for the `properties` argument.
109 -
        """
110 -
        if edge_ids is None:
111 -
            raise BluepySnapError("You need to set edge_ids in get.")
112 -
        if properties is None:
113 -
            Deprecate.warn(
114 -
                "Returning ids with get/properties is deprecated and will be removed in 1.0.0. "
115 -
                "Please use Edges.ids instead."
116 -
            )
117 -
            return edge_ids
118 -
        return super().get(edge_ids, properties)
80 +
    @property
81 +
    def size(self):
82 +
        """Population size."""
83 +
        return self._population.size
119 84
120 -
    def properties(self, edge_ids, properties):
121 -
        """Doc is overridden below."""
122 -
        Deprecate.warn(
123 -
            "Edges.properties function is deprecated and will be removed in 1.0.0. "
124 -
            "Please use Edges.get instead."
125 -
        )
126 -
        return self.get(edge_ids, properties)
85 +
    def _nodes(self, population_name):
86 +
        """Returns the NodePopulation corresponding to population."""
87 +
        result = self._circuit.nodes[population_name]
88 +
        return result
127 89
128 -
    properties.__doc__ = get.__doc__
90 +
    @property
91 +
    def type(self):
92 +
        """Population type."""
93 +
        return self._properties.type
129 94
130 -
    def afferent_nodes(self, target, unique=True):
131 -
        """Get afferent CircuitNodeIDs for given target ``node_id``.
95 +
    @cached_property
96 +
    def source(self):
97 +
        """Source NodePopulation."""
98 +
        return self._nodes(self._population.source)
132 99
133 -
        Notes:
134 -
            Afferent nodes are nodes projecting an outgoing edge to one of the ``target`` node.
100 +
    @cached_property
101 +
    def target(self):
102 +
        """Target NodePopulation."""
103 +
        return self._nodes(self._population.target)
135 104
136 -
        Args:
137 -
            target (CircuitNodeIds/int/sequence/str/mapping/None): the target you want to resolve
138 -
            and use as target nodes.
139 -
            unique (bool): If ``True``, return only unique afferent node IDs.
105 +
    @cached_property
106 +
    def _attribute_names(self):
107 +
        return set(self._population.attribute_names)
140 108
141 -
        Returns:
142 -
            CircuitNodeIDs: Afferent CircuitNodeIDs for all the targets from all edge population.
143 -
        """
144 -
        target_ids = self._circuit.nodes.ids(target)
145 -
        result = self._get_ids_from_pop(
146 -
            lambda x: (x.afferent_nodes(target_ids), x.source.name), CircuitNodeIds
147 -
        )
148 -
        if unique:
149 -
            result.unique(inplace=True)
150 -
        return result
109 +
    @cached_property
110 +
    def _dynamics_params_names(self):
111 +
        return set(utils.add_dynamic_prefix(self._population.dynamics_attribute_names))
151 112
152 -
    def efferent_nodes(self, source, unique=True):
153 -
        """Get efferent node IDs for given source ``node_id``.
113 +
    @property
114 +
    def _topology_property_names(self):
115 +
        return {Edge.SOURCE_NODE_ID, Edge.TARGET_NODE_ID}
116 +
117 +
    @property
118 +
    def property_names(self):
119 +
        """Set of available edge properties.
154 120
155 121
        Notes:
156 -
            Efferent nodes are nodes receiving an incoming edge from one of the ``source`` node.
122 +
            Properties are a combination of the group attributes, the dynamics_params and the
123 +
            topology properties.
124 +
        """
125 +
        return self._attribute_names | self._dynamics_params_names | self._topology_property_names
157 126
158 -
        Args:
159 -
            source (CircuitNodeIds/int/sequence/str/mapping/None): the source you want to resolve
160 -
                and use as source nodes.
161 -
            unique (bool): If ``True``, return only unique afferent node IDs.
127 +
    @cached_property
128 +
    def property_dtypes(self):
129 +
        """Returns the dtypes of all the properties.
162 130
163 131
        Returns:
164 -
            numpy.ndarray: Efferent node IDs for all the sources.
132 +
            pandas.Series: series indexed by field name with the corresponding dtype as value.
165 133
        """
166 -
        source_ids = self._circuit.nodes.ids(source)
167 -
        result = self._get_ids_from_pop(
168 -
            lambda x: (x.efferent_nodes(source_ids), x.target.name), CircuitNodeIds
169 -
        )
170 -
        if unique:
171 -
            result.unique(inplace=True)
172 -
        return result
134 +
        return self.get([0], list(self.property_names)).dtypes.sort_index()
173 135
174 -
    def pathway_edges(self, source=None, target=None, properties=None):
175 -
        """Get edges corresponding to ``source`` -> ``target`` connections.
136 +
    def container_property_names(self, container):
137 +
        """Lists the ConstContainer properties shared with the EdgePopulation.
176 138
177 139
        Args:
178 -
            source: source node group
179 -
            target: target node group
180 -
            properties: None / edge property name / list of edge property names
140 +
            container (ConstContainer): a container class for edge properties.
181 141
182 142
        Returns:
183 -
            CircuitEdgeIDs, if ``properties`` is None;
184 -
            Pandas Series indexed by CircuitEdgeIDs if ``properties`` is string;
185 -
            Pandas DataFrame indexed by CircuitEdgeIDs if ``properties`` is list.
143 +
            list: A list of strings corresponding to the properties that you can use from the
144 +
                container class
145 +
146 +
        Examples:
147 +
            >>> from bluepysnap.sonata_constants import Edge
148 +
            >>> print(my_edge_population.container_property_names(Edge))
149 +
            >>> ["AXONAL_DELAY", "SYN_WEIGHT"] # values you can use with my_edge_population
186 150
        """
187 -
        if source is None and target is None:
188 -
            raise BluepySnapError("Either `source` or `target` should be specified")
151 +
        if not inspect.isclass(container) or not issubclass(container, ConstContainer):
152 +
            raise BluepySnapError("'container' must be a subclass of ConstContainer")
153 +
        in_file = self.property_names
154 +
        return [k for k in container.key_set() if container.get(k) in in_file]
189 155
190 -
        source_ids = self._circuit.nodes.ids(source)
191 -
        target_ids = self._circuit.nodes.ids(target)
156 +
    def _get_property(self, prop, selection):
157 +
        if prop == Edge.SOURCE_NODE_ID:
158 +
            result = utils.ensure_ids(self._population.source_nodes(selection))
159 +
        elif prop == Edge.TARGET_NODE_ID:
160 +
            result = utils.ensure_ids(self._population.target_nodes(selection))
161 +
        elif prop in self._attribute_names:
162 +
            result = self._population.get_attribute(prop, selection)
163 +
        elif prop in self._dynamics_params_names:
164 +
            result = self._population.get_dynamics_attribute(
165 +
                prop.split(DYNAMICS_PREFIX)[1], selection
166 +
            )
167 +
        else:
168 +
            raise BluepySnapError(f"No such property: {prop}")
169 +
        return result
192 170
193 -
        result = self._get_ids_from_pop(
194 -
            lambda x: (x.pathway_edges(source_ids, target_ids), x.name), CircuitEdgeIds
195 -
        )
171 +
    def _get(self, selection, properties=None):
172 +
        """Get an array of edge IDs or DataFrame with edge properties."""
173 +
        edge_ids = utils.ensure_ids(selection.flatten())
174 +
        if properties is None:
175 +
            Deprecate.warn(
176 +
                "Returning ids with get/properties is deprecated and will be removed in 1.0.0. "
177 +
                "Please use EdgePopulation.ids instead."
178 +
            )
179 +
            return edge_ids
180 +
181 +
        if utils.is_iterable(properties):
182 +
            if len(edge_ids) == 0:
183 +
                result = pd.DataFrame(columns=properties)
184 +
            else:
185 +
                result = pd.DataFrame(index=edge_ids)
186 +
                for p in properties:
187 +
                    result[p] = self._get_property(p, selection)
188 +
        else:
189 +
            if len(edge_ids) == 0:
190 +
                result = pd.Series(name=properties, dtype=np.float64)
191 +
            else:
192 +
                result = pd.Series(
193 +
                    self._get_property(properties, selection), index=edge_ids, name=properties
194 +
                )
196 195
197 -
        if properties:
198 -
            return self.get(result, properties)
199 196
        return result
200 197
201 -
    def afferent_edges(self, node_id, properties=None):
202 -
        """Get afferent edges for given ``node_id``.
198 +
    def _edge_ids_by_filter(self, queries, raise_missing_prop):
199 +
        """Return edge IDs if their properties match the `queries` dict.
203 200
204 -
        Args:
205 -
            node_id (int): Target node ID.
206 -
            properties: An edge property name, a list of edge property names, or None.
201 +
        `props` values could be:
202 +
            pairs (range match for floating dtype fields)
203 +
            scalar or iterables (exact or "one of" match for other fields)
204 +
205 +
        You can use the special operators '$or' and '$and' also to combine different queries
206 +
        together.
207 +
208 +
        Examples:
209 +
            >>> self._edge_ids_by_filter({ Edge.POST_SECTION_ID: (0, 1),
210 +
            >>>                            Edge.AXONAL_DELAY: (.5, 2.) })
211 +
            >>> self._edge_ids_by_filter({'$or': [{ Edge.PRE_X_CENTER: [2, 3]},
212 +
            >>>                              { Edge.POST_SECTION_POS: (0, 1),
213 +
            >>>                              Edge.SYN_WEIGHT: (0.,1.4) }]})
214 +
215 +
        """
216 +
        properties = query.get_properties(queries)
217 +
        unknown_props = properties - self.property_names
218 +
        if raise_missing_prop and unknown_props:
219 +
            raise BluepySnapError(f"Unknown edge properties: {unknown_props}")
220 +
        res = []
221 +
        ids = self.ids(None)
222 +
        chunk_size = int(1e8)
223 +
        for chunk in np.array_split(ids, 1 + len(ids) // chunk_size):
224 +
            data = self.get(chunk, properties - unknown_props)
225 +
            res.extend(chunk[query.resolve_ids(data, self.name, queries)])
226 +
        return np.array(res, dtype=IDS_DTYPE)
227 +
228 +
    def ids(self, group=None, limit=None, sample=None, raise_missing_property=True):
229 +
        """Edge IDs corresponding to edges ``edge_ids``.
230 +
231 +
        Args:
232 +
            group (None/int/CircuitEdgeId/CircuitEdgeIds/sequence): Which IDs will be
233 +
                returned depends on the type of the ``group`` argument:
234 +
                - ``None``: return all IDs.
235 +
                - ``int``, ``CircuitEdgeId``: return a single edge ID.
236 +
                - ``CircuitEdgeIds`` return IDs of edges the edge population in an array.
237 +
                - ``sequence``: return IDs of edges in an array.
238 +
239 +
            sample (int): If specified, randomly choose ``sample`` number of
240 +
                IDs from the match result. If the size of the sample is greater than
241 +
                the size of the EdgePopulation then all ids are taken and shuffled.
242 +
243 +
            limit (int): If specified, return the first ``limit`` number of
244 +
                IDs from the match result. If limit is greater than the size of the population
245 +
                all node IDs are returned.
246 +
247 +
            raise_missing_property (bool): if True, raises if a property is not listed in this
248 +
                population. Otherwise the ids are just not selected if a property is missing.
249 +
250 +
        Returns:
251 +
            numpy.array: A numpy array of IDs.
252 +
        """
253 +
        if group is None:
254 +
            result = self._population.select_all().flatten()
255 +
        elif isinstance(group, CircuitEdgeIds):
256 +
            result = group.filter_population(self.name).get_ids()
257 +
        elif isinstance(group, np.ndarray):
258 +
            result = group
259 +
        elif isinstance(group, Mapping):
260 +
            result = self._edge_ids_by_filter(
261 +
                queries=group, raise_missing_prop=raise_missing_property
262 +
            )
263 +
        else:
264 +
            result = utils.ensure_list(group)
265 +
            # test if first value is a CircuitEdgeId if yes then all values must be CircuitEdgeId
266 +
            if isinstance(first(result, None), CircuitEdgeId):
267 +
                try:
268 +
                    result = [cid.id for cid in result if cid.population == self.name]
269 +
                except AttributeError as e:
270 +
                    raise BluepySnapError(
271 +
                        "All values from a list must be of type int or CircuitEdgeId."
272 +
                    ) from e
273 +
        if sample is not None:
274 +
            if len(result) > 0:
275 +
                result = np.random.choice(result, min(sample, len(result)), replace=False)
276 +
        if limit is not None:
277 +
            result = result[:limit]
278 +
        return utils.ensure_ids(result)
279 +
280 +
    def get(self, edge_ids, properties):
281 +
        """Edge properties as pandas DataFrame.
282 +
283 +
        Args:
284 +
            edge_ids (array-like): array-like of edge IDs
285 +
            properties (str/list): an edge property name or a list of edge property names
286 +
287 +
        Returns:
288 +
            pandas.Series/pandas.DataFrame:
289 +
                A pandas Series indexed by edge IDs if ``properties`` is scalar.
290 +
                A pandas DataFrame indexed by edge IDs if ``properties`` is list.
291 +
292 +
        Notes:
293 +
            The EdgePopulation.property_names function will give you all the usable properties
294 +
            for the `properties` argument.
295 +
        """
296 +
        edge_ids = self.ids(edge_ids)
297 +
        selection = libsonata.Selection(edge_ids)
298 +
        return self._get(selection, properties)
299 +
300 +
    def properties(self, edge_ids, properties):
301 +
        """Doc is overridden below."""
302 +
        Deprecate.warn(
303 +
            "EdgePopulation.properties function is deprecated and will be removed in 1.0.0. "
304 +
            "Please use EdgePopulation.get instead."
305 +
        )
306 +
        return self.get(edge_ids, properties)
307 +
308 +
    properties.__doc__ = get.__doc__
309 +
310 +
    def positions(self, edge_ids, side, kind):
311 +
        """Edge positions as a pandas DataFrame.
312 +
313 +
        Args:
314 +
            edge_ids (array-like): array-like of edge IDs
315 +
            side (str): ``afferent`` or ``efferent``
316 +
            kind (str): ``center`` or ``surface``
317 +
318 +
        Returns:
319 +
            Pandas Dataframe with ('x', 'y', 'z') columns indexed by edge IDs.
320 +
        """
321 +
        assert side in ("afferent", "efferent")
322 +
        assert kind in ("center", "surface")
323 +
        props = {f"{side}_{kind}_{p}": p for p in ["x", "y", "z"]}
324 +
        result = self.get(edge_ids, list(props))
325 +
        result.rename(columns=props, inplace=True)
326 +
        result.sort_index(axis=1, inplace=True)
327 +
        return result
328 +
329 +
    def afferent_nodes(self, target, unique=True):
330 +
        """Get afferent node IDs for given target ``node_id``.
331 +
332 +
        Notes:
333 +
            Afferent nodes are nodes projecting an outgoing edge to one of the ``target`` node.
334 +
335 +
        Args:
336 +
            target (CircuitNodeIds/int/sequence/str/mapping/None): the target you want to resolve
337 +
            and use as target nodes.
338 +
            unique (bool): If ``True``, return only unique afferent node IDs.
339 +
340 +
        Returns:
341 +
            numpy.ndarray: Afferent node IDs for all the targets.
342 +
        """
343 +
        if target is not None:
344 +
            selection = self._population.afferent_edges(self._resolve_node_ids(self.target, target))
345 +
        else:
346 +
            selection = self._population.select_all()
347 +
        result = self._population.source_nodes(selection)
348 +
        if unique:
349 +
            result = np.unique(result)
350 +
        return utils.ensure_ids(result)
351 +
352 +
    def efferent_nodes(self, source, unique=True):
353 +
        """Get efferent node IDs for given source ``node_id``.
354 +
355 +
        Notes:
356 +
            Efferent nodes are nodes receiving an incoming edge from one of the ``source`` node.
357 +
358 +
        Args:
359 +
            source (CircuitNodeIds/int/sequence/str/mapping/None): the source you want to resolve
360 +
                and use as source nodes.
361 +
            unique (bool): If ``True``, return only unique afferent node IDs.
362 +
363 +
        Returns:
364 +
            numpy.ndarray: Efferent node IDs for all the sources.
365 +
        """
366 +
        if source is not None:
367 +
            selection = self._population.efferent_edges(self._resolve_node_ids(self.source, source))
368 +
        else:
369 +
            selection = self._population.select_all()
370 +
        result = self._population.target_nodes(selection)
371 +
        if unique:
372 +
            result = np.unique(result)
373 +
        return utils.ensure_ids(result)
374 +
375 +
    def pathway_edges(self, source=None, target=None, properties=None):
376 +
        """Get edges corresponding to ``source`` -> ``target`` connections.
377 +
378 +
        Args:
379 +
            source (CircuitNodeIds/int/sequence/str/mapping/None): source node group
380 +
            target (CircuitNodeIds/int/sequence/str/mapping/None): target node group
381 +
            properties: None / edge property name / list of edge property names
382 +
383 +
        Returns:
384 +
            List of edge IDs, if ``properties`` is None;
385 +
            Pandas Series indexed by edge IDs if ``properties`` is string;
386 +
            Pandas DataFrame indexed by edge IDs if ``properties`` is list.
387 +
        """
388 +
        if source is None and target is None:
389 +
            raise BluepySnapError("Either `source` or `target` should be specified")
390 +
391 +
        source_node_ids = self._resolve_node_ids(self.source, source)
392 +
        target_edge_ids = self._resolve_node_ids(self.target, target)
393 +
394 +
        if source_node_ids is None:
395 +
            selection = self._population.afferent_edges(target_edge_ids)
396 +
        elif target_edge_ids is None:
397 +
            selection = self._population.efferent_edges(source_node_ids)
398 +
        else:
399 +
            selection = self._population.connecting_edges(source_node_ids, target_edge_ids)
400 +
401 +
        if properties:
402 +
            return self._get(selection, properties)
403 +
        return utils.ensure_ids(selection.flatten())
404 +
405 +
    def afferent_edges(self, node_id, properties=None):
406 +
        """Get afferent edges for given ``node_id``.
407 +
408 +
        Args:
409 +
            node_id (CircuitNodeIds/int/sequence/str/mapping/None) : Target node ID.
410 +
            properties: An edge property name, a list of edge property names, or None.
207 411
208 412
        Returns:
209 413
            pandas.Series/pandas.DataFrame/list:
@@ -217,7 +421,7 @@
Loading
217 421
        """Get efferent edges for given ``node_id``.
218 422
219 423
        Args:
220 -
            node_id: source node ID
424 +
            node_id (CircuitNodeIds/int/sequence/str/mapping/None): source node ID
221 425
            properties: None / edge property name / list of edge property names
222 426
223 427
        Returns:
@@ -231,8 +435,8 @@
Loading
231 435
        """Get edges corresponding to ``source_node_id`` -> ``target_node_id`` connection.
232 436
233 437
        Args:
234 -
            source_node_id: source node ID
235 -
            target_node_id: target node ID
438 +
            source_node_id (CircuitNodeIds/int/sequence/str/mapping/None): source node ID
439 +
            target_node_id (CircuitNodeIds/int/sequence/str/mapping/None): target node ID
236 440
            properties: None / edge property name / list of edge property names
237 441
238 442
        Returns:
@@ -244,47 +448,92 @@
Loading
244 448
            source=source_node_id, target=target_node_id, properties=properties
245 449
        )
246 450
247 -
    @staticmethod
248 -
    def _add_circuit_ids(its, source, target):
249 -
        """Generator comprehension adding the CircuitIds to the iterator.
451 +
    def _iter_connections(self, source_node_ids, target_node_ids, unique_node_ids, shuffle):
452 +
        """Iterate through `source_node_ids` -> `target_node_ids` connections."""
453 +
        # pylint: disable=too-many-branches,too-many-locals
454 +
        def _optimal_direction():
455 +
            """Choose between source and target node IDs for iterating."""
456 +
            if target_node_ids is None and source_node_ids is None:
457 +
                raise BluepySnapError("Either `source` or `target` should be specified")
458 +
            if source_node_ids is None:
459 +
                return "target"
460 +
            if target_node_ids is None:
461 +
                return "source"
462 +
            else:
463 +
                # Checking the indexing 'direction'. One direction has contiguous indices.
464 +
                range_size_source = _estimate_range_size(
465 +
                    self._population.efferent_edges, source_node_ids
466 +
                )
467 +
                range_size_target = _estimate_range_size(
468 +
                    self._population.afferent_edges, target_node_ids
469 +
                )
470 +
                return "source" if (range_size_source < range_size_target) else "target"
250 471
251 -
        Notes:
252 -
            Using closures or lambda functions would result in override functions and so the
253 -
            source and target would be the same for all the populations.
254 -
        """
255 -
        return (
256 -
            (CircuitNodeId(source, source_id), CircuitNodeId(target, target_id), count)
257 -
            for source_id, target_id, count in its
258 -
        )
472 +
        if _is_empty(source_node_ids) or _is_empty(target_node_ids):
473 +
            return
259 474
260 -
    @staticmethod
261 -
    def _add_edge_ids(its, source, target, pop_name):
262 -
        """Generator comprehension adding the CircuitIds to the iterator."""
263 -
        return (
264 -
            (
265 -
                CircuitNodeId(source, source_id),
266 -
                CircuitNodeId(target, target_id),
267 -
                CircuitEdgeIds.from_dict({pop_name: edge_id}),
268 -
            )
269 -
            for source_id, target_id, edge_id in its
270 -
        )
475 +
        direction = _optimal_direction()
476 +
        if direction == "target":
477 +
            primary_node_ids, secondary_node_ids = target_node_ids, source_node_ids
478 +
            get_connected_node_ids = self.afferent_nodes
479 +
        else:
480 +
            primary_node_ids, secondary_node_ids = source_node_ids, target_node_ids
481 +
            get_connected_node_ids = self.efferent_nodes
271 482
272 -
    @staticmethod
273 -
    def _omit_edge_count(its, source, target):
274 -
        """Generator comprehension adding the CircuitIds to the iterator."""
275 -
        return (
276 -
            (CircuitNodeId(source, source_id), CircuitNodeId(target, target_id))
277 -
            for source_id, target_id in its
278 -
        )
483 +
        primary_node_ids = np.unique(primary_node_ids)
484 +
        if shuffle:
485 +
            np.random.shuffle(primary_node_ids)
486 +
487 +
        if secondary_node_ids is not None:
488 +
            secondary_node_ids = np.unique(secondary_node_ids)
489 +
490 +
        secondary_node_ids_used = set()
491 +
492 +
        for key_node_id in primary_node_ids:
493 +
            connected_node_ids = get_connected_node_ids(key_node_id, unique=False)
494 +
            # [[secondary_node_id, count], ...]
495 +
            connected_node_ids_with_count = np.stack(
496 +
                np.unique(connected_node_ids, return_counts=True)
497 +
            ).transpose()
498 +
            # np.stack(uint64, int64) -> float64
499 +
            connected_node_ids_with_count = connected_node_ids_with_count.astype(np.uint32)
500 +
            if secondary_node_ids is not None:
501 +
                mask = np.in1d(
502 +
                    connected_node_ids_with_count[:, 0], secondary_node_ids, assume_unique=True
503 +
                )
504 +
                connected_node_ids_with_count = connected_node_ids_with_count[mask]
505 +
            if shuffle:
506 +
                np.random.shuffle(connected_node_ids_with_count)
507 +
508 +
            for conn_node_id, edge_count in connected_node_ids_with_count:
509 +
                if unique_node_ids and (conn_node_id in secondary_node_ids_used):
510 +
                    continue
511 +
                if direction == "target":
512 +
                    yield conn_node_id, key_node_id, edge_count
513 +
                else:
514 +
                    yield key_node_id, conn_node_id, edge_count
515 +
                if unique_node_ids:
516 +
                    secondary_node_ids_used.add(conn_node_id)
517 +
                    break
279 518
280 519
    def iter_connections(
281 -
        self, source=None, target=None, return_edge_ids=False, return_edge_count=False
520 +
        self,
521 +
        source=None,
522 +
        target=None,
523 +
        unique_node_ids=False,
524 +
        shuffle=False,
525 +
        return_edge_ids=False,
526 +
        return_edge_count=False,
282 527
    ):
283 528
        """Iterate through ``source`` -> ``target`` connections.
284 529
285 530
        Args:
286 531
            source (CircuitNodeIds/int/sequence/str/mapping/None): source node group
287 532
            target (CircuitNodeIds/int/sequence/str/mapping/None): target node group
533 +
            unique_node_ids: if True, no node ID will be used more than once as source or
534 +
                target for edges. Careful, this flag does not provide unique (source, target)
535 +
                pairs but unique node IDs.
536 +
            shuffle: if True, result order would be (somewhat) randomized
288 537
            return_edge_count: if True, edge count is added to yield result
289 538
            return_edge_ids: if True, edge ID list is added to yield result
290 539
@@ -299,272 +548,96 @@
Loading
299 548
            raise BluepySnapError(
300 549
                "`return_edge_count` and `return_edge_ids` are mutually exclusive"
301 550
            )
302 -
        for name, pop in self.items():
303 -
            it = pop.iter_connections(
304 -
                source=source,
305 -
                target=target,
306 -
                return_edge_ids=return_edge_ids,
307 -
                return_edge_count=return_edge_count,
308 -
            )
309 -
            source_pop = pop.source.name
310 -
            target_pop = pop.target.name
311 -
            if return_edge_count:
312 -
                yield from self._add_circuit_ids(it, source_pop, target_pop)
313 -
            elif return_edge_ids:
314 -
                yield from self._add_edge_ids(it, source_pop, target_pop, name)
315 -
            else:
316 -
                yield from self._omit_edge_count(it, source_pop, target_pop)
317 -
318 -
319 -
def _is_empty(xs):
320 -
    return (xs is not None) and (len(xs) == 0)
321 -
322 -
323 -
def _estimate_range_size(func, node_ids, n=3):
324 -
    """Median size of index second level for some node IDs from the provided list."""
325 -
    assert len(node_ids) > 0
326 -
    if len(node_ids) > n:
327 -
        node_ids = np.random.choice(node_ids, size=n, replace=False)
328 -
    return np.median([len(func(node_id).ranges) for node_id in node_ids])
329 -
330 -
331 -
class EdgePopulation:
332 -
    """Edge population access."""
333 -
334 -
    def __init__(self, circuit, population_name):
335 -
        """Initializes a EdgePopulation object from a EdgeStorage and a population name.
336 -
337 -
        Args:
338 -
            circuit (bluepysnap.Circuit): the circuit object containing the edge population
339 -
            population_name (str): the name of the edge population
340 -
341 -
        Returns:
342 -
            EdgePopulation: An EdgePopulation object.
343 -
        """
344 -
        self._circuit = circuit
345 -
        self.name = population_name
346 -
347 -
    @cached_property
348 -
    def _properties(self):
349 -
        return self._circuit.to_libsonata.edge_population_properties(self.name)
350 -
351 -
    @cached_property
352 -
    def _population(self):
353 -
        return self._circuit.to_libsonata.edge_population(self.name)
354 -
355 -
    @staticmethod
356 -
    def _resolve_node_ids(nodes, group):
357 -
        """Node IDs corresponding to node group filter."""
358 -
        if group is None:
359 -
            return None
360 -
        return nodes.ids(group)
361 -
362 -
    @property
363 -
    def size(self):
364 -
        """Population size."""
365 -
        return self._population.size
366 -
367 -
    def _nodes(self, population_name):
368 -
        """Returns the NodePopulation corresponding to population."""
369 -
        result = self._circuit.nodes[population_name]
370 -
        return result
371 551
372 -
    @property
373 -
    def type(self):
374 -
        """Population type."""
375 -
        return self._properties.type
552 +
        source_node_ids = self._resolve_node_ids(self.source, source)
553 +
        target_node_ids = self._resolve_node_ids(self.target, target)
376 554
377 -
    @cached_property
378 -
    def source(self):
379 -
        """Source NodePopulation."""
380 -
        return self._nodes(self._population.source)
555 +
        it = self._iter_connections(source_node_ids, target_node_ids, unique_node_ids, shuffle)
381 556
382 -
    @cached_property
383 -
    def target(self):
384 -
        """Target NodePopulation."""
385 -
        return self._nodes(self._population.target)
557 +
        if return_edge_count:
558 +
            return it
559 +
        elif return_edge_ids:
560 +
            add_edge_ids = lambda x: (x[0], x[1], self.pair_edges(x[0], x[1]))
561 +
            return map(add_edge_ids, it)
562 +
        else:
563 +
            omit_edge_count = lambda x: x[:2]
564 +
            return map(omit_edge_count, it)
386 565
387 566
    @cached_property
388 -
    def _attribute_names(self):
389 -
        return set(self._population.attribute_names)
567 +
    def h5_filepath(self):
568 +
        """Get the H5 edges file associated with population."""
569 +
        for edge_conf in self._circuit.config["networks"]["edges"]:
570 +
            if self.name in edge_conf["populations"]:
571 +
                return edge_conf["edges_file"]
572 +
        for edge_conf in self._circuit.config["networks"]["edges"]:
573 +
            h5_filepath = edge_conf["edges_file"]
574 +
            storage = libsonata.EdgeStorage(h5_filepath)
575 +
            if self.name in storage.population_names:  # pylint: disable=unsupported-membership-test
576 +
                return h5_filepath
577 +
        raise BluepySnapError(f"h5_filepath not found for population '{self.name}'")
390 578
391 -
    @cached_property
392 -
    def _dynamics_params_names(self):
393 -
        return set(utils.add_dynamic_prefix(self._population.dynamics_attribute_names))
394 579
395 -
    @property
396 -
    def _topology_property_names(self):
397 -
        return {Edge.SOURCE_NODE_ID, Edge.TARGET_NODE_ID}
580 +
class Edges(
581 +
    NetworkObject,
582 +
    metaclass=AbstractDocSubstitutionMeta,
583 +
    source_word="NetworkObject",
584 +
    target_word="Edge",
585 +
):
586 +
    """The top level Edges accessor."""
398 587
399 -
    @property
400 -
    def property_names(self):
401 -
        """Set of available edge properties.
588 +
    _population_class = EdgePopulation
402 589
403 -
        Notes:
404 -
            Properties are a combination of the group attributes, the dynamics_params and the
405 -
            topology properties.
406 -
        """
407 -
        return self._attribute_names | self._dynamics_params_names | self._topology_property_names
590 +
    def __init__(self, circuit):  # pylint: disable=useless-super-delegation
591 +
        """Initialize the top level Edges accessor."""
592 +
        super().__init__(circuit)
408 593
409 594
    @cached_property
410 -
    def property_dtypes(self):
411 -
        """Returns the dtypes of all the properties.
412 -
413 -
        Returns:
414 -
            pandas.Series: series indexed by field name with the corresponding dtype as value.
415 -
        """
416 -
        return self.get([0], list(self.property_names)).dtypes.sort_index()
417 -
418 -
    def container_property_names(self, container):
419 -
        """Lists the ConstContainer properties shared with the EdgePopulation.
420 -
421 -
        Args:
422 -
            container (ConstContainer): a container class for edge properties.
423 -
424 -
        Returns:
425 -
            list: A list of strings corresponding to the properties that you can use from the
426 -
                container class
427 -
428 -
        Examples:
429 -
            >>> from bluepysnap.sonata_constants import Edge
430 -
            >>> print(my_edge_population.container_property_names(Edge))
431 -
            >>> ["AXONAL_DELAY", "SYN_WEIGHT"] # values you can use with my_edge_population
432 -
        """
433 -
        if not inspect.isclass(container) or not issubclass(container, ConstContainer):
434 -
            raise BluepySnapError("'container' must be a subclass of ConstContainer")
435 -
        in_file = self.property_names
436 -
        return [k for k in container.key_set() if container.get(k) in in_file]
437 -
438 -
    def _get_property(self, prop, selection):
439 -
        if prop == Edge.SOURCE_NODE_ID:
440 -
            result = utils.ensure_ids(self._population.source_nodes(selection))
441 -
        elif prop == Edge.TARGET_NODE_ID:
442 -
            result = utils.ensure_ids(self._population.target_nodes(selection))
443 -
        elif prop in self._attribute_names:
444 -
            result = self._population.get_attribute(prop, selection)
445 -
        elif prop in self._dynamics_params_names:
446 -
            result = self._population.get_dynamics_attribute(
447 -
                prop.split(DYNAMICS_PREFIX)[1], selection
448 -
            )
449 -
        else:
450 -
            raise BluepySnapError(f"No such property: {prop}")
451 -
        return result
452 -
453 -
    def _get(self, selection, properties=None):
454 -
        """Get an array of edge IDs or DataFrame with edge properties."""
455 -
        edge_ids = utils.ensure_ids(selection.flatten())
456 -
        if properties is None:
457 -
            Deprecate.warn(
458 -
                "Returning ids with get/properties is deprecated and will be removed in 1.0.0. "
459 -
                "Please use EdgePopulation.ids instead."
460 -
            )
461 -
            return edge_ids
462 -
463 -
        if utils.is_iterable(properties):
464 -
            if len(edge_ids) == 0:
465 -
                result = pd.DataFrame(columns=properties)
466 -
            else:
467 -
                result = pd.DataFrame(index=edge_ids)
468 -
                for p in properties:
469 -
                    result[p] = self._get_property(p, selection)
470 -
        else:
471 -
            if len(edge_ids) == 0:
472 -
                result = pd.Series(name=properties, dtype=np.float64)
473 -
            else:
474 -
                result = pd.Series(
475 -
                    self._get_property(properties, selection), index=edge_ids, name=properties
476 -
                )
477 -
478 -
        return result
479 -
480 -
    def _edge_ids_by_filter(self, queries, raise_missing_prop):
481 -
        """Return edge IDs if their properties match the `queries` dict.
482 -
483 -
        `props` values could be:
484 -
            pairs (range match for floating dtype fields)
485 -
            scalar or iterables (exact or "one of" match for other fields)
486 -
487 -
        You can use the special operators '$or' and '$and' also to combine different queries
488 -
        together.
489 -
490 -
        Examples:
491 -
            >>> self._edge_ids_by_filter({ Edge.POST_SECTION_ID: (0, 1),
492 -
            >>>                            Edge.AXONAL_DELAY: (.5, 2.) })
493 -
            >>> self._edge_ids_by_filter({'$or': [{ Edge.PRE_X_CENTER: [2, 3]},
494 -
            >>>                              { Edge.POST_SECTION_POS: (0, 1),
495 -
            >>>                              Edge.SYN_WEIGHT: (0.,1.4) }]})
496 -
497 -
        """
498 -
        properties = query.get_properties(queries)
499 -
        unknown_props = properties - self.property_names
500 -
        if raise_missing_prop and unknown_props:
501 -
            raise BluepySnapError(f"Unknown edge properties: {unknown_props}")
502 -
        res = []
503 -
        ids = self.ids(None)
504 -
        chunk_size = int(1e8)
505 -
        for chunk in np.array_split(ids, 1 + len(ids) // chunk_size):
506 -
            data = self.get(chunk, properties - unknown_props)
507 -
            res.extend(chunk[query.resolve_ids(data, self.name, queries)])
508 -
        return np.array(res, dtype=IDS_DTYPE)
595 +
    def population_names(self):
596 +
        """Defines all sorted edge population names from the Circuit."""
597 +
        return sorted(self._circuit.to_libsonata.edge_populations)
509 598
510 -
    def ids(self, group=None, limit=None, sample=None, raise_missing_property=True):
511 -
        """Edge IDs corresponding to edges ``edge_ids``.
599 +
    def ids(self, group=None, sample=None, limit=None):
600 +
        """Edge CircuitEdgeIds corresponding to edges ``edge_ids``.
512 601
513 602
        Args:
514 603
            group (None/int/CircuitEdgeId/CircuitEdgeIds/sequence): Which IDs will be
515 -
                returned depends on the type of the ``group`` argument:
516 -
                - ``None``: return all IDs.
517 -
                - ``int``, ``CircuitEdgeId``: return a single edge ID.
518 -
                - ``CircuitEdgeIds`` return IDs of edges the edge population in an array.
519 -
                - ``sequence``: return IDs of edges in an array.
520 -
604 +
            returned depends on the type of the ``group`` argument:
605 +
                - ``None``: return all CircuitEdgeIds.
606 +
                - ``CircuitEdgeId``: return the ID in a CircuitEdgeIds object.
607 +
                - ``CircuitEdgeIds``: return the IDs in a CircuitNodeIds object.
608 +
                - ``int``: returns a CircuitEdgeIds object containing the corresponding edge ID
609 +
                    for all populations.
610 +
                - ``sequence``: returns a CircuitEdgeIds object containing the corresponding edge
611 +
                    IDs for all populations.
521 612
            sample (int): If specified, randomly choose ``sample`` number of
522 613
                IDs from the match result. If the size of the sample is greater than
523 -
                the size of the EdgePopulation then all ids are taken and shuffled.
524 -
614 +
                the size of all the EdgePopulations then all ids are taken and shuffled.
525 615
            limit (int): If specified, return the first ``limit`` number of
526 -
                IDs from the match result. If limit is greater than the size of the population
616 +
                IDs from the match result. If limit is greater than the size of all the populations
527 617
                all node IDs are returned.
528 618
529 -
            raise_missing_property (bool): if True, raises if a property is not listed in this
530 -
                population. Otherwise the ids are just not selected if a property is missing.
531 -
532 619
        Returns:
533 -
            numpy.array: A numpy array of IDs.
620 +
            CircuitEdgeIds: returns a CircuitEdgeIds containing all the edge IDs and the
621 +
                corresponding populations. For performance reasons we do not test if the edge ids
622 +
                are present or not in the circuit.
623 +
624 +
        Notes:
625 +
            This envision also the maybe future selection of edges on queries.
534 626
        """
535 -
        if group is None:
536 -
            result = self._population.select_all().flatten()
537 -
        elif isinstance(group, CircuitEdgeIds):
538 -
            result = group.filter_population(self.name).get_ids()
539 -
        elif isinstance(group, np.ndarray):
540 -
            result = group
541 -
        elif isinstance(group, Mapping):
542 -
            result = self._edge_ids_by_filter(
543 -
                queries=group, raise_missing_prop=raise_missing_property
544 -
            )
545 -
        else:
546 -
            result = utils.ensure_list(group)
547 -
            # test if first value is a CircuitEdgeId if yes then all values must be CircuitEdgeId
548 -
            if isinstance(first(result, None), CircuitEdgeId):
549 -
                try:
550 -
                    result = [cid.id for cid in result if cid.population == self.name]
551 -
                except AttributeError as e:
552 -
                    raise BluepySnapError(
553 -
                        "All values from a list must be of type int or CircuitEdgeId."
554 -
                    ) from e
555 -
        if sample is not None:
556 -
            if len(result) > 0:
557 -
                result = np.random.choice(result, min(sample, len(result)), replace=False)
558 -
        if limit is not None:
559 -
            result = result[:limit]
560 -
        return utils.ensure_ids(result)
627 +
        if isinstance(group, CircuitEdgeIds):
628 +
            diff = np.setdiff1d(group.get_populations(unique=True), self.population_names)
629 +
            if diff.size != 0:
630 +
                raise BluepySnapError(f"Population {diff} does not exist in the circuit.")
631 +
        fun = lambda x: (x.ids(group), x.name)
632 +
        return self._get_ids_from_pop(fun, CircuitEdgeIds, sample=sample, limit=limit)
561 633
562 -
    def get(self, edge_ids, properties):
634 +
    def get(self, edge_ids=None, properties=None):  # pylint: disable=arguments-renamed
563 635
        """Edge properties as pandas DataFrame.
564 636
565 637
        Args:
566 -
            edge_ids (array-like): array-like of edge IDs
567 -
            properties (str/list): an edge property name or a list of edge property names
638 +
            edge_ids (int/CircuitEdgeId/CircuitEdgeIds/sequence): same as Edges.ids().
639 +
            properties (None/str/list): an edge property name or a list of edge property names.
640 +
                If set to None ids are returned.
568 641
569 642
        Returns:
570 643
            pandas.Series/pandas.DataFrame:
@@ -572,44 +645,31 @@
Loading
572 645
                A pandas DataFrame indexed by edge IDs if ``properties`` is list.
573 646
574 647
        Notes:
575 -
            The EdgePopulation.property_names function will give you all the usable properties
648 +
            The Edges.property_names function will give you all the usable properties
576 649
            for the `properties` argument.
577 650
        """
578 -
        edge_ids = self.ids(edge_ids)
579 -
        selection = libsonata.Selection(edge_ids)
580 -
        return self._get(selection, properties)
651 +
        if edge_ids is None:
652 +
            raise BluepySnapError("You need to set edge_ids in get.")
653 +
        if properties is None:
654 +
            Deprecate.warn(
655 +
                "Returning ids with get/properties is deprecated and will be removed in 1.0.0. "
656 +
                "Please use Edges.ids instead."
657 +
            )
658 +
            return edge_ids
659 +
        return super().get(edge_ids, properties)
581 660
582 661
    def properties(self, edge_ids, properties):
583 662
        """Doc is overridden below."""
584 663
        Deprecate.warn(
585 -
            "EdgePopulation.properties function is deprecated and will be removed in 1.0.0. "
586 -
            "Please use EdgePopulation.get instead."
664 +
            "Edges.properties function is deprecated and will be removed in 1.0.0. "
665 +
            "Please use Edges.get instead."
587 666
        )
588 667
        return self.get(edge_ids, properties)
589 668
590 669
    properties.__doc__ = get.__doc__
591 670
592 -
    def positions(self, edge_ids, side, kind):
593 -
        """Edge positions as a pandas DataFrame.
594 -
595 -
        Args:
596 -
            edge_ids (array-like): array-like of edge IDs
597 -
            side (str): ``afferent`` or ``efferent``
598 -
            kind (str): ``center`` or ``surface``
599 -
600 -
        Returns:
601 -
            Pandas Dataframe with ('x', 'y', 'z') columns indexed by edge IDs.
602 -
        """
603 -
        assert side in ("afferent", "efferent")
604 -
        assert kind in ("center", "surface")
605 -
        props = {f"{side}_{kind}_{p}": p for p in ["x", "y", "z"]}
606 -
        result = self.get(edge_ids, list(props))
607 -
        result.rename(columns=props, inplace=True)
608 -
        result.sort_index(axis=1, inplace=True)
609 -
        return result
610 -
611 671
    def afferent_nodes(self, target, unique=True):
612 -
        """Get afferent node IDs for given target ``node_id``.
672 +
        """Get afferent CircuitNodeIDs for given target ``node_id``.
613 673
614 674
        Notes:
615 675
            Afferent nodes are nodes projecting an outgoing edge to one of the ``target`` node.
@@ -620,16 +680,15 @@
Loading
620 680
            unique (bool): If ``True``, return only unique afferent node IDs.
621 681
622 682
        Returns:
623 -
            numpy.ndarray: Afferent node IDs for all the targets.
683 +
            CircuitNodeIDs: Afferent CircuitNodeIDs for all the targets from all edge population.
624 684
        """
625 -
        if target is not None:
626 -
            selection = self._population.afferent_edges(self._resolve_node_ids(self.target, target))
627 -
        else:
628 -
            selection = self._population.select_all()
629 -
        result = self._population.source_nodes(selection)
685 +
        target_ids = self._circuit.nodes.ids(target)
686 +
        result = self._get_ids_from_pop(
687 +
            lambda x: (x.afferent_nodes(target_ids), x.source.name), CircuitNodeIds
688 +
        )
630 689
        if unique:
631 -
            result = np.unique(result)
632 -
        return utils.ensure_ids(result)
690 +
            result.unique(inplace=True)
691 +
        return result
633 692
634 693
    def efferent_nodes(self, source, unique=True):
635 694
        """Get efferent node IDs for given source ``node_id``.
@@ -645,50 +704,46 @@
Loading
645 704
        Returns:
646 705
            numpy.ndarray: Efferent node IDs for all the sources.
647 706
        """
648 -
        if source is not None:
649 -
            selection = self._population.efferent_edges(self._resolve_node_ids(self.source, source))
650 -
        else:
651 -
            selection = self._population.select_all()
652 -
        result = self._population.target_nodes(selection)
707 +
        source_ids = self._circuit.nodes.ids(source)
708 +
        result = self._get_ids_from_pop(
709 +
            lambda x: (x.efferent_nodes(source_ids), x.target.name), CircuitNodeIds
710 +
        )
653 711
        if unique:
654 -
            result = np.unique(result)
655 -
        return utils.ensure_ids(result)
712 +
            result.unique(inplace=True)
713 +
        return result
656 714
657 715
    def pathway_edges(self, source=None, target=None, properties=None):
658 716
        """Get edges corresponding to ``source`` -> ``target`` connections.
659 717
660 718
        Args:
661 -
            source (CircuitNodeIds/int/sequence/str/mapping/None): source node group
662 -
            target (CircuitNodeIds/int/sequence/str/mapping/None): target node group
719 +
            source: source node group
720 +
            target: target node group
663 721
            properties: None / edge property name / list of edge property names
664 722
665 723
        Returns:
666 -
            List of edge IDs, if ``properties`` is None;
667 -
            Pandas Series indexed by edge IDs if ``properties`` is string;
668 -
            Pandas DataFrame indexed by edge IDs if ``properties`` is list.
724 +
            CircuitEdgeIDs, if ``properties`` is None;
725 +
            Pandas Series indexed by CircuitEdgeIDs if ``properties`` is string;
726 +
            Pandas DataFrame indexed by CircuitEdgeIDs if ``properties`` is list.
669 727
        """
670 728
        if source is None and target is None:
671 729
            raise BluepySnapError("Either `source` or `target` should be specified")
672 730
673 -
        source_node_ids = self._resolve_node_ids(self.source, source)
674 -
        target_edge_ids = self._resolve_node_ids(self.target, target)
731 +
        source_ids = self._circuit.nodes.ids(source)
732 +
        target_ids = self._circuit.nodes.ids(target)
675 733
676 -
        if source_node_ids is None:
677 -
            selection = self._population.afferent_edges(target_edge_ids)
678 -
        elif target_edge_ids is None:
679 -
            selection = self._population.efferent_edges(source_node_ids)
680 -
        else:
681 -
            selection = self._population.connecting_edges(source_node_ids, target_edge_ids)
734 +
        result = self._get_ids_from_pop(
735 +
            lambda x: (x.pathway_edges(source_ids, target_ids), x.name), CircuitEdgeIds
736 +
        )
682 737
683 738
        if properties:
684 -
            return self._get(selection, properties)
685 -
        return utils.ensure_ids(selection.flatten())
739 +
            return self.get(result, properties)
740 +
        return result
686 741
687 742
    def afferent_edges(self, node_id, properties=None):
688 743
        """Get afferent edges for given ``node_id``.
689 744
690 745
        Args:
691 -
            node_id (CircuitNodeIds/int/sequence/str/mapping/None) : Target node ID.
746 +
            node_id (int): Target node ID.
692 747
            properties: An edge property name, a list of edge property names, or None.
693 748
694 749
        Returns:
@@ -703,7 +758,7 @@
Loading
703 758
        """Get efferent edges for given ``node_id``.
704 759
705 760
        Args:
706 -
            node_id (CircuitNodeIds/int/sequence/str/mapping/None): source node ID
761 +
            node_id: source node ID
707 762
            properties: None / edge property name / list of edge property names
708 763
709 764
        Returns:
@@ -717,8 +772,8 @@
Loading
717 772
        """Get edges corresponding to ``source_node_id`` -> ``target_node_id`` connection.
718 773
719 774
        Args:
720 -
            source_node_id (CircuitNodeIds/int/sequence/str/mapping/None): source node ID
721 -
            target_node_id (CircuitNodeIds/int/sequence/str/mapping/None): target node ID
775 +
            source_node_id: source node ID
776 +
            target_node_id: target node ID
722 777
            properties: None / edge property name / list of edge property names
723 778
724 779
        Returns:
@@ -730,92 +785,47 @@
Loading
730 785
            source=source_node_id, target=target_node_id, properties=properties
731 786
        )
732 787
733 -
    def _iter_connections(self, source_node_ids, target_node_ids, unique_node_ids, shuffle):
734 -
        """Iterate through `source_node_ids` -> `target_node_ids` connections."""
735 -
        # pylint: disable=too-many-branches,too-many-locals
736 -
        def _optimal_direction():
737 -
            """Choose between source and target node IDs for iterating."""
738 -
            if target_node_ids is None and source_node_ids is None:
739 -
                raise BluepySnapError("Either `source` or `target` should be specified")
740 -
            if source_node_ids is None:
741 -
                return "target"
742 -
            if target_node_ids is None:
743 -
                return "source"
744 -
            else:
745 -
                # Checking the indexing 'direction'. One direction has contiguous indices.
746 -
                range_size_source = _estimate_range_size(
747 -
                    self._population.efferent_edges, source_node_ids
748 -
                )
749 -
                range_size_target = _estimate_range_size(
750 -
                    self._population.afferent_edges, target_node_ids
751 -
                )
752 -
                return "source" if (range_size_source < range_size_target) else "target"
753 -
754 -
        if _is_empty(source_node_ids) or _is_empty(target_node_ids):
755 -
            return
756 -
757 -
        direction = _optimal_direction()
758 -
        if direction == "target":
759 -
            primary_node_ids, secondary_node_ids = target_node_ids, source_node_ids
760 -
            get_connected_node_ids = self.afferent_nodes
761 -
        else:
762 -
            primary_node_ids, secondary_node_ids = source_node_ids, target_node_ids
763 -
            get_connected_node_ids = self.efferent_nodes
764 -
765 -
        primary_node_ids = np.unique(primary_node_ids)
766 -
        if shuffle:
767 -
            np.random.shuffle(primary_node_ids)
768 -
769 -
        if secondary_node_ids is not None:
770 -
            secondary_node_ids = np.unique(secondary_node_ids)
788 +
    @staticmethod
789 +
    def _add_circuit_ids(its, source, target):
790 +
        """Generator comprehension adding the CircuitIds to the iterator.
771 791
772 -
        secondary_node_ids_used = set()
792 +
        Notes:
793 +
            Using closures or lambda functions would result in override functions and so the
794 +
            source and target would be the same for all the populations.
795 +
        """
796 +
        return (
797 +
            (CircuitNodeId(source, source_id), CircuitNodeId(target, target_id), count)
798 +
            for source_id, target_id, count in its
799 +
        )
773 800
774 -
        for key_node_id in primary_node_ids:
775 -
            connected_node_ids = get_connected_node_ids(key_node_id, unique=False)
776 -
            # [[secondary_node_id, count], ...]
777 -
            connected_node_ids_with_count = np.stack(
778 -
                np.unique(connected_node_ids, return_counts=True)
779 -
            ).transpose()
780 -
            # np.stack(uint64, int64) -> float64
781 -
            connected_node_ids_with_count = connected_node_ids_with_count.astype(np.uint32)
782 -
            if secondary_node_ids is not None:
783 -
                mask = np.in1d(
784 -
                    connected_node_ids_with_count[:, 0], secondary_node_ids, assume_unique=True
785 -
                )
786 -
                connected_node_ids_with_count = connected_node_ids_with_count[mask]
787 -
            if shuffle:
788 -
                np.random.shuffle(connected_node_ids_with_count)
801 +
    @staticmethod
802 +
    def _add_edge_ids(its, source, target, pop_name):
803 +
        """Generator comprehension adding the CircuitIds to the iterator."""
804 +
        return (
805 +
            (
806 +
                CircuitNodeId(source, source_id),
807 +
                CircuitNodeId(target, target_id),
808 +
                CircuitEdgeIds.from_dict({pop_name: edge_id}),
809 +
            )
810 +
            for source_id, target_id, edge_id in its
811 +
        )
789 812
790 -
            for conn_node_id, edge_count in connected_node_ids_with_count:
791 -
                if unique_node_ids and (conn_node_id in secondary_node_ids_used):
792 -
                    continue
793 -
                if direction == "target":
794 -
                    yield conn_node_id, key_node_id, edge_count
795 -
                else:
796 -
                    yield key_node_id, conn_node_id, edge_count
797 -
                if unique_node_ids:
798 -
                    secondary_node_ids_used.add(conn_node_id)
799 -
                    break
813 +
    @staticmethod
814 +
    def _omit_edge_count(its, source, target):
815 +
        """Generator comprehension adding the CircuitIds to the iterator."""
816 +
        return (
817 +
            (CircuitNodeId(source, source_id), CircuitNodeId(target, target_id))
818 +
            for source_id, target_id in its
819 +
        )
800 820
801 821
    def iter_connections(
802 -
        self,
803 -
        source=None,
804 -
        target=None,
805 -
        unique_node_ids=False,
806 -
        shuffle=False,
807 -
        return_edge_ids=False,
808 -
        return_edge_count=False,
822 +
        self, source=None, target=None, return_edge_ids=False, return_edge_count=False
809 823
    ):
810 824
        """Iterate through ``source`` -> ``target`` connections.
811 825
812 826
        Args:
813 827
            source (CircuitNodeIds/int/sequence/str/mapping/None): source node group
814 828
            target (CircuitNodeIds/int/sequence/str/mapping/None): target node group
815 -
            unique_node_ids: if True, no node ID will be used more than once as source or
816 -
                target for edges. Careful, this flag does not provide unique (source, target)
817 -
                pairs but unique node IDs.
818 -
            shuffle: if True, result order would be (somewhat) randomized
819 829
            return_edge_count: if True, edge count is added to yield result
820 830
            return_edge_ids: if True, edge ID list is added to yield result
821 831
@@ -830,27 +840,18 @@
Loading
830 840
            raise BluepySnapError(
831 841
                "`return_edge_count` and `return_edge_ids` are mutually exclusive"
832 842
            )
833 -
834 -
        source_node_ids = self._resolve_node_ids(self.source, source)
835 -
        target_node_ids = self._resolve_node_ids(self.target, target)
836 -
837 -
        it = self._iter_connections(source_node_ids, target_node_ids, unique_node_ids, shuffle)
838 -
839 -
        if return_edge_count:
840 -
            return it
841 -
        elif return_edge_ids:
842 -
            add_edge_ids = lambda x: (x[0], x[1], self.pair_edges(x[0], x[1]))
843 -
            return map(add_edge_ids, it)
844 -
        else:
845 -
            omit_edge_count = lambda x: x[:2]
846 -
            return map(omit_edge_count, it)
847 -
848 -
    @cached_property
849 -
    def h5_filepath(self):
850 -
        """Get the H5 edges file associated with population."""
851 -
        for edge_conf in self._circuit.config["networks"]["edges"]:
852 -
            h5_filepath = edge_conf["edges_file"]
853 -
            storage = libsonata.EdgeStorage(h5_filepath)
854 -
            if self.name in storage.population_names:  # pylint: disable=unsupported-membership-test
855 -
                return h5_filepath
856 -
        raise BluepySnapError(f"h5_filepath not found for population '{self.name}'")
843 +
        for name, pop in self.items():
844 +
            it = pop.iter_connections(
845 +
                source=source,
846 +
                target=target,
847 +
                return_edge_ids=return_edge_ids,
848 +
                return_edge_count=return_edge_count,
849 +
            )
850 +
            source_pop = pop.source.name
851 +
            target_pop = pop.target.name
852 +
            if return_edge_count:
853 +
                yield from self._add_circuit_ids(it, source_pop, target_pop)
854 +
            elif return_edge_ids:
855 +
                yield from self._add_edge_ids(it, source_pop, target_pop, name)
856 +
            else:
857 +
                yield from self._omit_edge_count(it, source_pop, target_pop)

@@ -53,8 +53,6 @@
Loading
53 53
        https://github.com/AllenInstitute/sonata/blob/master/docs/SONATA_DEVELOPER_GUIDE.md#network_config
54 54
    """
55 55
56 -
    # TODO: Can be simplified by a great deal after libsonata SimulationConfig parses the manifest
57 -
58 56
    def __init__(self, config, config_dir):
59 57
        """Initializes a Resolver object.
60 58
Files Coverage
bluepysnap 100.00%
Project Totals (23 files) 100.00%
bluepysnap-py39
Build #2738433460 -
pytest

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading