bmtool 0.7.5.1__tar.gz → 0.7.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bmtool-0.7.5.1 → bmtool-0.7.7}/PKG-INFO +1 -1
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/bmplot/connections.py +11 -5
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/connectors.py +386 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/singlecell.py +429 -31
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/synapses.py +369 -45
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/util/util.py +69 -17
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool.egg-info/PKG-INFO +1 -1
- {bmtool-0.7.5.1 → bmtool-0.7.7}/setup.py +1 -1
- {bmtool-0.7.5.1 → bmtool-0.7.7}/LICENSE +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/README.md +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/SLURM.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/__init__.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/__main__.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/analysis/__init__.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/analysis/entrainment.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/analysis/lfp.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/analysis/netcon_reports.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/analysis/spikes.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/bmplot/__init__.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/bmplot/entrainment.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/bmplot/lfp.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/bmplot/netcon_reports.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/bmplot/spikes.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/debug/__init__.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/debug/commands.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/debug/debug.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/graphs.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/manage.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/plot_commands.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/util/__init__.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/util/commands.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/util/neuron/__init__.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool/util/neuron/celltuner.py +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool.egg-info/SOURCES.txt +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool.egg-info/dependency_links.txt +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool.egg-info/entry_points.txt +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool.egg-info/requires.txt +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/bmtool.egg-info/top_level.txt +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/pyproject.toml +0 -0
- {bmtool-0.7.5.1 → bmtool-0.7.7}/setup.cfg +0 -0
@@ -154,6 +154,7 @@ def percent_connection_matrix(
|
|
154
154
|
save_file=None,
|
155
155
|
method="total",
|
156
156
|
include_gap=True,
|
157
|
+
return_dict = False
|
157
158
|
):
|
158
159
|
"""
|
159
160
|
Generates a plot showing the percent connectivity of a network
|
@@ -197,8 +198,12 @@ def percent_connection_matrix(
|
|
197
198
|
if title is None or title == "":
|
198
199
|
title = "Percent Connectivity"
|
199
200
|
|
200
|
-
|
201
|
-
|
201
|
+
if return_dict:
|
202
|
+
dict = plot_connection_info(text, num, source_labels, target_labels, title, save_file=save_file, return_dict=return_dict)
|
203
|
+
return dict
|
204
|
+
else:
|
205
|
+
plot_connection_info(text, num, source_labels, target_labels, title, save_file=save_file)
|
206
|
+
return
|
202
207
|
|
203
208
|
|
204
209
|
def probability_connection_matrix(
|
@@ -661,7 +666,8 @@ def connection_histogram(
|
|
661
666
|
(edges[source_id_type] == source_id) & (edges[target_id_type] == target_id)
|
662
667
|
]
|
663
668
|
if not include_gap:
|
664
|
-
|
669
|
+
gap_col = temp["is_gap_junction"].fillna(False).astype(bool)
|
670
|
+
temp = temp[~gap_col]
|
665
671
|
node_pairs = temp.groupby("target_node_id")["source_node_id"].count()
|
666
672
|
try:
|
667
673
|
conn_mean = statistics.mean(node_pairs.values)
|
@@ -1022,8 +1028,8 @@ def plot_synapse_location(config: str, source: str, target: str, sids: str, tids
|
|
1022
1028
|
)
|
1023
1029
|
|
1024
1030
|
# Fix the validation logic - it was using 'or' instead of 'and'
|
1025
|
-
if syn_feature not in ["afferent_section_id", "afferent_section_pos"]:
|
1026
|
-
|
1031
|
+
#if syn_feature not in ["afferent_section_id", "afferent_section_pos"]:
|
1032
|
+
# raise ValueError("Currently only syn features supported are afferent_section_id or afferent_section_pos")
|
1027
1033
|
|
1028
1034
|
try:
|
1029
1035
|
# Load mechanisms and template
|
@@ -1604,6 +1604,392 @@ class CorrelatedGapJunction(GapJunction):
|
|
1604
1604
|
if self.save_report:
|
1605
1605
|
self.save_connection_report()
|
1606
1606
|
return nsyns
|
1607
|
+
|
1608
|
+
class GapJunctionConditionalReciprocalConnector(AbstractConnector):
|
1609
|
+
"""
|
1610
|
+
Object for building reciprocal chemical synapses in BMTK network model with
|
1611
|
+
probabilities that depend on the presence of gap junctions between cell pairs.
|
1612
|
+
|
1613
|
+
This connector creates chemical synapses where the connection probabilities
|
1614
|
+
(forward, backward, and reciprocal) differ based on whether a pair of cells
|
1615
|
+
shares a gap junction. This allows modeling of the experimentally observed
|
1616
|
+
correlation between electrical and chemical coupling in neural populations.
|
1617
|
+
|
1618
|
+
Algorithm:
|
1619
|
+
For each potential connection pair, first determine if a gap junction exists
|
1620
|
+
using the provided gap_connector. Then apply different bivariate Bernoulli
|
1621
|
+
probability distributions for chemical synapses based on electrical coupling
|
1622
|
+
status:
|
1623
|
+
- Electrically coupled pairs: Use p0_elec, p1_elec, pr_elec probabilities
|
1624
|
+
- Non-electrically coupled pairs: Use p0_nonelec, p1_nonelec, pr_nonelec probabilities
|
1625
|
+
|
1626
|
+
For each pair, generate random connections following the same bivariate
|
1627
|
+
Bernoulli distribution as ReciprocalConnector, but with conditional
|
1628
|
+
probabilities based on gap junction presence.
|
1629
|
+
|
1630
|
+
Use with BMTK:
|
1631
|
+
1. First create and set up a gap junction connector:
|
1632
|
+
|
1633
|
+
gap_connector = GapJunction(p=0.08, verbose=True)
|
1634
|
+
gap_connector.setup_nodes(source_population, target_population)
|
1635
|
+
net.add_edges(is_gap_junction=True, **gap_connector.edge_params())
|
1636
|
+
|
1637
|
+
2. Create the conditional reciprocal connector with different probabilities
|
1638
|
+
for electrically coupled vs non-coupled pairs:
|
1639
|
+
|
1640
|
+
chemical_connector = GapJunctionConditionalReciprocalConnector(
|
1641
|
+
gap_connector=gap_connector,
|
1642
|
+
p0_elec=0.50, p1_elec=0.50, pr_elec=0.25, # High reciprocity for coupled pairs
|
1643
|
+
p0_nonelec=0.125, p1_nonelec=0.125, pr_nonelec=0.03, # Low reciprocity for non-coupled pairs
|
1644
|
+
verbose=True
|
1645
|
+
)
|
1646
|
+
|
1647
|
+
3. Set up nodes and add chemical edges:
|
1648
|
+
|
1649
|
+
chemical_connector.setup_nodes(source_population, target_population)
|
1650
|
+
net.add_edges(**chemical_connector.edge_params(),
|
1651
|
+
**chemical_synapse_properties)
|
1652
|
+
|
1653
|
+
4. Build the network:
|
1654
|
+
|
1655
|
+
net.build()
|
1656
|
+
|
1657
|
+
Parameters:
|
1658
|
+
gap_connector: GapJunction connector object that has been set up with nodes.
|
1659
|
+
Used to determine which cell pairs have electrical coupling.
|
1660
|
+
p0_elec, p1_elec: Forward and backward connection probabilities for
|
1661
|
+
electrically coupled pairs. Can be constants or functions within [0, 1].
|
1662
|
+
pr_elec: Reciprocal connection probability for electrically coupled pairs.
|
1663
|
+
Can be a constant or function accepting (pr_arg, p0, p1) arguments.
|
1664
|
+
p0_nonelec, p1_nonelec: Forward and backward connection probabilities for
|
1665
|
+
non-electrically coupled pairs. Can be constants or functions within [0, 1].
|
1666
|
+
pr_nonelec: Reciprocal connection probability for non-electrically coupled pairs.
|
1667
|
+
Can be a constant or function accepting (pr_arg, p0, p1) arguments.
|
1668
|
+
p0_elec_arg, p1_elec_arg, pr_elec_arg: Input arguments for electrically coupled
|
1669
|
+
probability functions. Can be constants, distance functions, or other
|
1670
|
+
node property functions. Set to None if functions don't need arguments.
|
1671
|
+
p0_nonelec_arg, p1_nonelec_arg, pr_nonelec_arg: Input arguments for
|
1672
|
+
non-electrically coupled probability functions, similar to above.
|
1673
|
+
n_syn0, n_syn1: Number of synapses for forward/backward connections if
|
1674
|
+
established. Can be constants or functions of node properties.
|
1675
|
+
Limited to 255 due to uint8 storage.
|
1676
|
+
verbose: Whether to print detailed connection statistics and progress.
|
1677
|
+
save_report: Whether to save connection report to CSV file.
|
1678
|
+
report_name: Filename for connection report (default: "conn.csv").
|
1679
|
+
|
1680
|
+
Returns:
|
1681
|
+
An object that works with BMTK to build conditional reciprocal chemical
|
1682
|
+
edges in a network, with probabilities dependent on gap junction presence.
|
1683
|
+
|
1684
|
+
Important attributes:
|
1685
|
+
gap_connector: Reference to the GapJunction connector used for coupling detection.
|
1686
|
+
vars: Dictionary storing original input parameters.
|
1687
|
+
source, target: NodePool objects for source and target populations.
|
1688
|
+
recurrent: Whether source and target populations are the same.
|
1689
|
+
conn_mat: Connection matrix storing synapse counts.
|
1690
|
+
conn_prop: List of dictionaries storing connection properties for forward
|
1691
|
+
and backward connections. Format: [{src_id: {tgt_id: prop}, ...}, ...]
|
1692
|
+
gap_decisions: Dictionary caching gap junction presence for each pair.
|
1693
|
+
connection_stats: Statistics tracking connections by electrical coupling status.
|
1694
|
+
Format: {'elec': {'pairs': int, 'uni': int, 'recp': int},
|
1695
|
+
'nonelec': {'pairs': int, 'uni': int, 'recp': int}}
|
1696
|
+
"""
|
1697
|
+
|
1698
|
+
def __init__(self, gap_connector, p0_elec, p1_elec, pr_elec, p0_nonelec, p1_nonelec, pr_nonelec,
|
1699
|
+
p0_elec_arg=None, p1_elec_arg=None, pr_elec_arg=None,
|
1700
|
+
p0_nonelec_arg=None, p1_nonelec_arg=None, pr_nonelec_arg=None,
|
1701
|
+
n_syn0=1, n_syn1=1, verbose=True, save_report=True, report_name=None):
|
1702
|
+
# Store original parameters like ReciprocalConnector
|
1703
|
+
args = locals()
|
1704
|
+
var_set = ("p0_elec", "p0_elec_arg", "p1_elec", "p1_elec_arg", "pr_elec", "pr_elec_arg",
|
1705
|
+
"p0_nonelec", "p0_nonelec_arg", "p1_nonelec", "p1_nonelec_arg", "pr_nonelec", "pr_nonelec_arg",
|
1706
|
+
"n_syn0", "n_syn1")
|
1707
|
+
self.vars = {key: args[key] for key in var_set}
|
1708
|
+
|
1709
|
+
self.gap_connector = gap_connector
|
1710
|
+
self.verbose = verbose
|
1711
|
+
self.save_report = save_report
|
1712
|
+
self.report_name = report_name or "conn.csv"
|
1713
|
+
self.conn_prop = [{}, {}]
|
1714
|
+
self.stage = 0
|
1715
|
+
# Track gap junction decisions and connections for detailed reporting
|
1716
|
+
self.gap_decisions = {}
|
1717
|
+
self.connection_stats = {'elec': {'pairs': 0, 'uni': 0, 'recp': 0},
|
1718
|
+
'nonelec': {'pairs': 0, 'uni': 0, 'recp': 0}}
|
1719
|
+
|
1720
|
+
def setup_variables(self):
|
1721
|
+
"""Set up variables like ReciprocalConnector does"""
|
1722
|
+
callable_set = set()
|
1723
|
+
# Make constant variables constant functions
|
1724
|
+
for name, var in self.vars.items():
|
1725
|
+
if callable(var):
|
1726
|
+
callable_set.add(name) # record callable variables
|
1727
|
+
setattr(self, name, var)
|
1728
|
+
else:
|
1729
|
+
setattr(self, name, self.constant_function(var))
|
1730
|
+
callable_set.add(name) # constants converted to functions are also callable
|
1731
|
+
self.callable_set = callable_set
|
1732
|
+
|
1733
|
+
# Make callable variables accept index input instead of node input
|
1734
|
+
# Exclude probability functions (p0_elec, p1_elec, pr_elec, p0_nonelec, p1_nonelec, pr_nonelec)
|
1735
|
+
# as they are called with arguments from _arg functions
|
1736
|
+
for name in callable_set - {"p0_elec", "p1_elec", "pr_elec", "p0_nonelec", "p1_nonelec", "pr_nonelec"}:
|
1737
|
+
var = getattr(self, name) # Get the already converted function
|
1738
|
+
setattr(self, name, self.node_2_idx_input(var, '1' in name and name.startswith(('p1_', 'pr_', 'n_syn1'))))
|
1739
|
+
|
1740
|
+
@staticmethod
|
1741
|
+
def constant_function(val):
|
1742
|
+
"""Convert a constant to a constant function"""
|
1743
|
+
def constant(*arg):
|
1744
|
+
return val
|
1745
|
+
return constant
|
1746
|
+
|
1747
|
+
def node_2_idx_input(self, var_func, reverse=False):
|
1748
|
+
"""Convert a function that accept nodes as input to accept indices as input"""
|
1749
|
+
if reverse:
|
1750
|
+
def idx_2_var(j, i):
|
1751
|
+
return var_func(self.target_list[j], self.source_list[i])
|
1752
|
+
else:
|
1753
|
+
def idx_2_var(i, j):
|
1754
|
+
return var_func(self.source_list[i], self.target_list[j])
|
1755
|
+
return idx_2_var
|
1756
|
+
|
1757
|
+
def setup_nodes(self, source, target):
|
1758
|
+
self.source = source
|
1759
|
+
self.target = target
|
1760
|
+
self.recurrent = is_same_pop(self.source, self.target)
|
1761
|
+
self.source_ids = [s.node_id for s in self.source]
|
1762
|
+
self.n_source = len(self.source_ids)
|
1763
|
+
self.source_list = list(self.source)
|
1764
|
+
if self.recurrent:
|
1765
|
+
self.target_ids = self.source_ids
|
1766
|
+
self.n_target = self.n_source
|
1767
|
+
self.target_list = self.source_list
|
1768
|
+
else:
|
1769
|
+
self.target_ids = [t.node_id for t in self.target]
|
1770
|
+
self.n_target = len(self.target_ids)
|
1771
|
+
self.target_list = list(self.target)
|
1772
|
+
|
1773
|
+
def edge_params(self):
|
1774
|
+
if self.stage == 0:
|
1775
|
+
params = {
|
1776
|
+
"source": self.source,
|
1777
|
+
"target": self.target,
|
1778
|
+
"iterator": "one_to_all",
|
1779
|
+
"connection_rule": self.make_forward_connection,
|
1780
|
+
}
|
1781
|
+
else:
|
1782
|
+
params = {
|
1783
|
+
"source": self.target,
|
1784
|
+
"target": self.source,
|
1785
|
+
"iterator": "all_to_one",
|
1786
|
+
"connection_rule": self.make_backward_connection,
|
1787
|
+
}
|
1788
|
+
self.stage += 1
|
1789
|
+
return params
|
1790
|
+
|
1791
|
+
def has_gap(self, source_node, target_node):
|
1792
|
+
"""Check if a gap junction exists between two cells by checking the gap_connector's connections"""
|
1793
|
+
sid = source_node.node_id
|
1794
|
+
tid = target_node.node_id
|
1795
|
+
|
1796
|
+
# Check if this pair has a gap junction recorded in the gap_connector
|
1797
|
+
# Since gap junctions are bidirectional, check both directions
|
1798
|
+
return (sid in self.gap_connector.conn_prop and tid in self.gap_connector.conn_prop[sid]) or \
|
1799
|
+
(tid in self.gap_connector.conn_prop and sid in self.gap_connector.conn_prop[tid])
|
1800
|
+
|
1801
|
+
def cond_backward_prob(self, forward, p0, p1, pr):
|
1802
|
+
"""Calculate conditional probability of backward connection given forward result"""
|
1803
|
+
if p0 > 0:
|
1804
|
+
# Ensure pr is within valid bounds
|
1805
|
+
pr_min = max(0, p0 + p1 - 1)
|
1806
|
+
pr_max = min(p0, p1)
|
1807
|
+
pr = max(pr_min, min(pr_max, pr))
|
1808
|
+
|
1809
|
+
if forward:
|
1810
|
+
return pr / p0
|
1811
|
+
else:
|
1812
|
+
return (p1 - pr) / (1 - p0) if p1 > pr else 0.0
|
1813
|
+
else:
|
1814
|
+
return p1
|
1815
|
+
|
1816
|
+
def make_forward_connection(self, source, targets, *args, **kwargs):
|
1817
|
+
if not hasattr(self, 'conn_mat'):
|
1818
|
+
self.initialize()
|
1819
|
+
stage_idx = self.stage - 1
|
1820
|
+
nsyns = self.conn_mat[stage_idx, self.iter_count, :]
|
1821
|
+
self.iter_count += 1
|
1822
|
+
if self.iter_count == self.n_source and stage_idx == self.end_stage:
|
1823
|
+
if self.verbose:
|
1824
|
+
self.connection_number_info()
|
1825
|
+
return nsyns
|
1826
|
+
|
1827
|
+
def make_backward_connection(self, targets, source, *args, **kwargs):
|
1828
|
+
self.stage = 2
|
1829
|
+
return self.make_forward_connection(source, targets)
|
1830
|
+
|
1831
|
+
def calc_pair(self, i, j, is_elec):
|
1832
|
+
"""Calculate probability values for a pair like ReciprocalConnector does"""
|
1833
|
+
if is_elec:
|
1834
|
+
p0_arg = self.p0_elec_arg(i, j)
|
1835
|
+
p1_arg = self.p1_elec_arg(j, i)
|
1836
|
+
p0 = self.p0_elec(p0_arg)
|
1837
|
+
p1 = self.p1_elec(p1_arg)
|
1838
|
+
pr = self.pr_elec(self.pr_elec_arg(i, j), p0, p1)
|
1839
|
+
else:
|
1840
|
+
p0_arg = self.p0_nonelec_arg(i, j)
|
1841
|
+
p1_arg = self.p1_nonelec_arg(j, i)
|
1842
|
+
p0 = self.p0_nonelec(p0_arg)
|
1843
|
+
p1 = self.p1_nonelec(p1_arg)
|
1844
|
+
pr = self.pr_nonelec(self.pr_nonelec_arg(i, j), p0, p1)
|
1845
|
+
return p0, p1, pr
|
1846
|
+
|
1847
|
+
def initialize(self):
|
1848
|
+
self.setup_variables() # Set up variables like ReciprocalConnector
|
1849
|
+
self.end_stage = 0 if self.recurrent else 1
|
1850
|
+
shape = (self.end_stage + 1, self.n_source, self.n_target)
|
1851
|
+
self.conn_mat = np.zeros(shape, dtype=np.uint8)
|
1852
|
+
self.iter_count = 0
|
1853
|
+
|
1854
|
+
if self.verbose:
|
1855
|
+
self.timer = Timer()
|
1856
|
+
|
1857
|
+
# Pre-generate gap junction connections for consistency
|
1858
|
+
# Store gap decisions with symmetric pair keys
|
1859
|
+
self.gap_decisions = {}
|
1860
|
+
|
1861
|
+
# Generate connections using proper bivariate Bernoulli distribution
|
1862
|
+
for i in range(self.n_source):
|
1863
|
+
for j in range(self.n_target):
|
1864
|
+
# Skip self-connections for recurrent networks
|
1865
|
+
if self.recurrent and i >= j:
|
1866
|
+
continue
|
1867
|
+
|
1868
|
+
sid = self.source_ids[i]
|
1869
|
+
tid = self.target_ids[j]
|
1870
|
+
source_node = self.source_list[i]
|
1871
|
+
target_node = self.target_list[j]
|
1872
|
+
|
1873
|
+
# Check or generate gap junction decision with symmetric key
|
1874
|
+
pair_key = (min(sid, tid), max(sid, tid)) # Ensure consistent ordering
|
1875
|
+
if pair_key not in self.gap_decisions:
|
1876
|
+
self.gap_decisions[pair_key] = self.has_gap(source_node, target_node)
|
1877
|
+
has_gap = self.gap_decisions[pair_key]
|
1878
|
+
|
1879
|
+
# Track pair counts for statistics
|
1880
|
+
if has_gap:
|
1881
|
+
self.connection_stats['elec']['pairs'] += 1
|
1882
|
+
p0, p1, pr = self.calc_pair(i, j, True)
|
1883
|
+
else:
|
1884
|
+
self.connection_stats['nonelec']['pairs'] += 1
|
1885
|
+
p0, p1, pr = self.calc_pair(i, j, False)
|
1886
|
+
|
1887
|
+
# First decide forward connection
|
1888
|
+
forward = decision(p0)
|
1889
|
+
|
1890
|
+
# Then decide backward connection based on forward result
|
1891
|
+
backward_prob = self.cond_backward_prob(forward, p0, p1, pr)
|
1892
|
+
backward = decision(backward_prob)
|
1893
|
+
|
1894
|
+
# Track connection statistics
|
1895
|
+
if forward and backward:
|
1896
|
+
# Reciprocal connection
|
1897
|
+
if has_gap:
|
1898
|
+
self.connection_stats['elec']['recp'] += 1
|
1899
|
+
else:
|
1900
|
+
self.connection_stats['nonelec']['recp'] += 1
|
1901
|
+
elif forward or backward:
|
1902
|
+
# Unidirectional connection
|
1903
|
+
if has_gap:
|
1904
|
+
self.connection_stats['elec']['uni'] += 1
|
1905
|
+
else:
|
1906
|
+
self.connection_stats['nonelec']['uni'] += 1
|
1907
|
+
|
1908
|
+
# Set connections in matrix
|
1909
|
+
if forward:
|
1910
|
+
self.conn_mat[0, i, j] = self.n_syn0(i, j)
|
1911
|
+
self.add_conn_prop(i, j, None, 0)
|
1912
|
+
|
1913
|
+
if backward:
|
1914
|
+
if self.recurrent:
|
1915
|
+
self.conn_mat[0, j, i] = self.n_syn1(j, i)
|
1916
|
+
self.add_conn_prop(j, i, None, 0)
|
1917
|
+
else:
|
1918
|
+
self.conn_mat[1, i, j] = self.n_syn1(i, j)
|
1919
|
+
self.add_conn_prop(i, j, None, 1)
|
1920
|
+
|
1921
|
+
if self.verbose:
|
1922
|
+
self.timer.report("Time for creating connection matrix")
|
1923
|
+
if self.save_report:
|
1924
|
+
self.save_connection_report()
|
1925
|
+
|
1926
|
+
def add_conn_prop(self, src, trg, prop, stage=0):
|
1927
|
+
sid = self.source_ids[src]
|
1928
|
+
tid = self.target_ids[trg]
|
1929
|
+
if stage:
|
1930
|
+
sid, tid = tid, sid
|
1931
|
+
trg_dict = self.conn_prop[stage].setdefault(sid, {})
|
1932
|
+
trg_dict[tid] = prop
|
1933
|
+
|
1934
|
+
def connection_number_info(self):
|
1935
|
+
conn_mat = self.conn_mat.astype(bool)
|
1936
|
+
|
1937
|
+
if self.recurrent:
|
1938
|
+
# Calculate total pairs (upper triangle only to avoid double counting)
|
1939
|
+
n_pairs = (self.n_source * (self.n_source - 1)) // 2
|
1940
|
+
|
1941
|
+
# Count reciprocal connections (both i->j and j->i exist)
|
1942
|
+
n_recp = np.count_nonzero(conn_mat[0] & conn_mat[0].T) // 2
|
1943
|
+
# Count total connections
|
1944
|
+
n_total = np.count_nonzero(conn_mat[0])
|
1945
|
+
# Unidirectional = total - 2*reciprocal
|
1946
|
+
n_uni = n_total - 2 * n_recp
|
1947
|
+
|
1948
|
+
# Print detailed breakdown by electrical coupling
|
1949
|
+
print("Detailed connection statistics by electrical coupling:")
|
1950
|
+
print("=" * 60)
|
1951
|
+
|
1952
|
+
# Electrically coupled pairs
|
1953
|
+
elec_pairs = self.connection_stats['elec']['pairs']
|
1954
|
+
elec_uni = self.connection_stats['elec']['uni']
|
1955
|
+
elec_recp = self.connection_stats['elec']['recp']
|
1956
|
+
|
1957
|
+
print(f"Electrically coupled pairs ({elec_pairs} pairs, {elec_pairs/n_pairs:.1%} of total):")
|
1958
|
+
print(f" Unidirectional: {elec_uni} ({elec_uni/elec_pairs:.1%} of elec pairs)")
|
1959
|
+
print(f" Bidirectional: {elec_recp} ({elec_recp/elec_pairs:.1%} of elec pairs)")
|
1960
|
+
print(f" No connection: {elec_pairs - elec_uni - elec_recp} ({(elec_pairs - elec_uni - elec_recp)/elec_pairs:.1%} of elec pairs)")
|
1961
|
+
|
1962
|
+
# Non-electrically coupled pairs
|
1963
|
+
nonelec_pairs = self.connection_stats['nonelec']['pairs']
|
1964
|
+
nonelec_uni = self.connection_stats['nonelec']['uni']
|
1965
|
+
nonelec_recp = self.connection_stats['nonelec']['recp']
|
1966
|
+
|
1967
|
+
print(f"\nNon-electrically coupled pairs ({nonelec_pairs} pairs, {nonelec_pairs/n_pairs:.1%} of total):")
|
1968
|
+
print(f" Unidirectional: {nonelec_uni} ({nonelec_uni/nonelec_pairs:.1%} of nonelec pairs)")
|
1969
|
+
print(f" Bidirectional: {nonelec_recp} ({nonelec_recp/nonelec_pairs:.1%} of nonelec pairs)")
|
1970
|
+
print(f" No connection: {nonelec_pairs - nonelec_uni - nonelec_recp} ({(nonelec_pairs - nonelec_uni - nonelec_recp)/nonelec_pairs:.1%} of nonelec pairs)")
|
1971
|
+
|
1972
|
+
print(f"\nOverall chemical connectivity:")
|
1973
|
+
print(f" Numbers of connections: unidirectional, reciprocal")
|
1974
|
+
print(f" Number of connected pairs: ({n_uni}, {n_recp})")
|
1975
|
+
print(f" Fraction of connected pairs: ({n_uni/n_pairs:.2%}, {n_recp/n_pairs:.2%})")
|
1976
|
+
print(f" Total chemical connectivity: {(n_uni + n_recp)/n_pairs:.2%}")
|
1977
|
+
|
1978
|
+
else:
|
1979
|
+
# For non-recurrent networks
|
1980
|
+
n_pairs = self.n_source * self.n_target
|
1981
|
+
n_forward = np.count_nonzero(conn_mat[0])
|
1982
|
+
n_backward = np.count_nonzero(conn_mat[1])
|
1983
|
+
n_recp = np.count_nonzero(conn_mat[0] & conn_mat[1])
|
1984
|
+
|
1985
|
+
print("Numbers of connections: forward, backward, reciprocal")
|
1986
|
+
print(f"Number of connected pairs: ({n_forward}, {n_backward}, {n_recp})")
|
1987
|
+
print(f"Fraction of connected pairs: ({n_forward/n_pairs:.2%}, {n_backward/n_pairs:.2%}, {n_recp/n_pairs:.2%})")
|
1988
|
+
|
1989
|
+
def save_connection_report(self):
|
1990
|
+
# Implement similar to ReciprocalConnector if needed
|
1991
|
+
pass
|
1992
|
+
|
1607
1993
|
|
1608
1994
|
|
1609
1995
|
class OneToOneSequentialConnector(AbstractConnector):
|