bmtool 0.7.7__py3-none-any.whl → 0.7.8.1__py3-none-any.whl
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.
Potentially problematic release.
This version of bmtool might be problematic. Click here for more details.
- bmtool/SLURM.py +15 -0
- bmtool/analysis/entrainment.py +113 -0
- bmtool/analysis/lfp.py +1 -1
- bmtool/bmplot/connections.py +756 -339
- bmtool/bmplot/entrainment.py +169 -49
- bmtool/bmplot/lfp.py +146 -11
- bmtool/bmplot/netcon_reports.py +1 -0
- bmtool/bmplot/spikes.py +175 -18
- bmtool/singlecell.py +47 -2
- bmtool/synapses.py +896 -87
- {bmtool-0.7.7.dist-info → bmtool-0.7.8.1.dist-info}/METADATA +1 -1
- {bmtool-0.7.7.dist-info → bmtool-0.7.8.1.dist-info}/RECORD +16 -16
- {bmtool-0.7.7.dist-info → bmtool-0.7.8.1.dist-info}/WHEEL +0 -0
- {bmtool-0.7.7.dist-info → bmtool-0.7.8.1.dist-info}/entry_points.txt +0 -0
- {bmtool-0.7.7.dist-info → bmtool-0.7.8.1.dist-info}/licenses/LICENSE +0 -0
- {bmtool-0.7.7.dist-info → bmtool-0.7.8.1.dist-info}/top_level.txt +0 -0
bmtool/synapses.py
CHANGED
|
@@ -173,6 +173,7 @@ class SynapseTuner:
|
|
|
173
173
|
self.other_vars_to_record = other_vars_to_record or []
|
|
174
174
|
self.ispk = None
|
|
175
175
|
self.input_mode = False # Add input_mode attribute
|
|
176
|
+
self.last_figure = None # Store reference to last generated figure
|
|
176
177
|
|
|
177
178
|
# Store original slider_vars for connection switching
|
|
178
179
|
self.original_slider_vars = slider_vars or list(self.synaptic_props.keys())
|
|
@@ -1045,7 +1046,9 @@ class SynapseTuner:
|
|
|
1045
1046
|
for j in range(num_vars_to_plot, len(axs)):
|
|
1046
1047
|
fig.delaxes(axs[j])
|
|
1047
1048
|
|
|
1048
|
-
#
|
|
1049
|
+
#plt.tight_layout()
|
|
1050
|
+
fig.suptitle(f"Connection: {self.current_connection}")
|
|
1051
|
+
self.last_figure = plt.gcf()
|
|
1049
1052
|
plt.show()
|
|
1050
1053
|
|
|
1051
1054
|
def _set_drive_train(self, freq=50.0, delay=250.0):
|
|
@@ -1148,7 +1151,7 @@ class SynapseTuner:
|
|
|
1148
1151
|
|
|
1149
1152
|
def _calc_ppr_induction_recovery(self, amp, normalize_by_trial=True, print_math=True):
|
|
1150
1153
|
"""
|
|
1151
|
-
Calculates paired-pulse ratio, induction, and
|
|
1154
|
+
Calculates paired-pulse ratio, induction, recovery, and simple PPR metrics from response amplitudes.
|
|
1152
1155
|
|
|
1153
1156
|
Parameters:
|
|
1154
1157
|
-----------
|
|
@@ -1163,13 +1166,15 @@ class SynapseTuner:
|
|
|
1163
1166
|
--------
|
|
1164
1167
|
tuple
|
|
1165
1168
|
A tuple containing:
|
|
1166
|
-
- ppr: Paired-pulse ratio (2nd pulse
|
|
1169
|
+
- ppr: Paired-pulse ratio (2nd pulse - 1st pulse) normalized by 90th percentile amplitude
|
|
1167
1170
|
- induction: Measure of facilitation/depression during initial pulses
|
|
1168
1171
|
- recovery: Measure of recovery after the delay period
|
|
1172
|
+
- simple_ppr: Simple paired-pulse ratio (2nd pulse / 1st pulse)
|
|
1169
1173
|
|
|
1170
1174
|
Notes:
|
|
1171
1175
|
------
|
|
1172
|
-
- PPR >
|
|
1176
|
+
- PPR > 0 indicates facilitation, PPR < 0 indicates depression
|
|
1177
|
+
- Simple PPR > 1 indicates facilitation, Simple PPR < 1 indicates depression
|
|
1173
1178
|
- Induction > 0 indicates facilitation, Induction < 0 indicates depression
|
|
1174
1179
|
- Recovery compares the response after delay to the initial pulses
|
|
1175
1180
|
"""
|
|
@@ -1190,34 +1195,44 @@ class SynapseTuner:
|
|
|
1190
1195
|
f"Short Term Plasticity Results for {self.train_freq}Hz with {self.train_delay} Delay"
|
|
1191
1196
|
)
|
|
1192
1197
|
print("=" * 40)
|
|
1193
|
-
print("PPR: Above
|
|
1194
|
-
print("
|
|
1195
|
-
print("
|
|
1198
|
+
print("Simple PPR: Above 1 is facilitating, below 1 is depressing")
|
|
1199
|
+
print("PPR: Above 0 is facilitating, below 0 is depressing.")
|
|
1200
|
+
print("Induction: Above 0 is facilitating, below 0 is depressing.")
|
|
1201
|
+
print("Recovery: A measure of how fast STP decays.\n")
|
|
1202
|
+
|
|
1203
|
+
# Simple PPR Calculation: Avg 2nd pulse / Avg 1st pulse
|
|
1204
|
+
simple_ppr = np.mean(amp[:, 1:2]) / np.mean(amp[:, 0:1])
|
|
1205
|
+
print("Simple Paired Pulse Ratio (PPR)")
|
|
1206
|
+
print(" Calculation: Avg 2nd pulse / Avg 1st pulse")
|
|
1207
|
+
print(
|
|
1208
|
+
f" Values: {np.mean(amp[:, 1:2]):.3f} / {np.mean(amp[:, 0:1]):.3f} = {simple_ppr:.3f}\n"
|
|
1209
|
+
)
|
|
1196
1210
|
|
|
1197
1211
|
# PPR Calculation: (Avg 2nd pulse - Avg 1st pulse) / 90th percentile amplitude
|
|
1198
1212
|
ppr = (np.mean(amp[:, 1:2]) - np.mean(amp[:, 0:1])) / percentile_90
|
|
1199
1213
|
print("Paired Pulse Response (PPR)")
|
|
1200
|
-
print("Calculation: (Avg 2nd pulse - Avg 1st pulse) / 90th percentile amplitude")
|
|
1214
|
+
print(" Calculation: (Avg 2nd pulse - Avg 1st pulse) / 90th percentile amplitude")
|
|
1201
1215
|
print(
|
|
1202
|
-
f"Values: ({np.mean(amp[:, 1:2]):.3f} - {np.mean(amp[:, 0:1]):.3f}) / {percentile_90:.3f} = {ppr:.3f}\n"
|
|
1216
|
+
f" Values: ({np.mean(amp[:, 1:2]):.3f} - {np.mean(amp[:, 0:1]):.3f}) / {percentile_90:.3f} = {ppr:.3f}\n"
|
|
1203
1217
|
)
|
|
1218
|
+
|
|
1204
1219
|
|
|
1205
1220
|
# Induction Calculation: (Avg (6th, 7th, 8th pulses) - Avg 1st pulse) / 90th percentile amplitude
|
|
1206
1221
|
induction = (np.mean(amp[:, 5:8]) - np.mean(amp[:, :1])) / percentile_90
|
|
1207
1222
|
print("Induction")
|
|
1208
|
-
print("Calculation: (Avg(6th, 7th, 8th pulses) - Avg 1st pulse) / 90th percentile amplitude")
|
|
1223
|
+
print(" Calculation: (Avg(6th, 7th, 8th pulses) - Avg 1st pulse) / 90th percentile amplitude")
|
|
1209
1224
|
print(
|
|
1210
|
-
f"Values: {np.mean(amp[:, 5:8]):.3f} - {np.mean(amp[:, :1]):.3f} / {percentile_90:.3f} = {induction:.3f}\n"
|
|
1225
|
+
f" Values: {np.mean(amp[:, 5:8]):.3f} - {np.mean(amp[:, :1]):.3f} / {percentile_90:.3f} = {induction:.3f}\n"
|
|
1211
1226
|
)
|
|
1212
1227
|
|
|
1213
1228
|
# Recovery Calculation: (Avg (9th, 10th, 11th, 12th pulses) - Avg (1st, 2nd, 3rd, 4th pulses)) / 90th percentile amplitude
|
|
1214
1229
|
recovery = (np.mean(amp[:, 8:12]) - np.mean(amp[:, :4])) / percentile_90
|
|
1215
1230
|
print("Recovery")
|
|
1216
1231
|
print(
|
|
1217
|
-
"Calculation: (Avg(9th, 10th, 11th, 12th pulses) - Avg(1st to 4th pulses)) / 90th percentile amplitude"
|
|
1232
|
+
" Calculation: (Avg(9th, 10th, 11th, 12th pulses) - Avg(1st to 4th pulses)) / 90th percentile amplitude"
|
|
1218
1233
|
)
|
|
1219
1234
|
print(
|
|
1220
|
-
f"Values: {np.mean(amp[:, 8:12]):.3f} - {np.mean(amp[:, :4]):.3f} / {percentile_90:.3f} = {recovery:.3f}\n"
|
|
1235
|
+
f" Values: {np.mean(amp[:, 8:12]):.3f} - {np.mean(amp[:, :4]):.3f} / {percentile_90:.3f} = {recovery:.3f}\n"
|
|
1221
1236
|
)
|
|
1222
1237
|
|
|
1223
1238
|
print("=" * 40 + "\n")
|
|
@@ -1226,8 +1241,9 @@ class SynapseTuner:
|
|
|
1226
1241
|
ppr = (np.mean(amp[:, 1:2]) - np.mean(amp[:, 0:1])) / percentile_90
|
|
1227
1242
|
induction = (np.mean(amp[:, 5:8]) - np.mean(amp[:, :1])) / percentile_90
|
|
1228
1243
|
recovery = (np.mean(amp[:, 8:12]) - np.mean(amp[:, :4])) / percentile_90
|
|
1244
|
+
simple_ppr = np.mean(amp[:, 1:2]) / np.mean(amp[:, 0:1])
|
|
1229
1245
|
|
|
1230
|
-
return ppr, induction, recovery
|
|
1246
|
+
return ppr, induction, recovery, simple_ppr
|
|
1231
1247
|
|
|
1232
1248
|
def _set_syn_prop(self, **kwargs):
|
|
1233
1249
|
"""
|
|
@@ -1268,19 +1284,19 @@ class SynapseTuner:
|
|
|
1268
1284
|
for i in range(3):
|
|
1269
1285
|
self.vcl.amp[i] = self.conn["spec_settings"]["vclamp_amp"]
|
|
1270
1286
|
self.vcl.dur[i] = vcldur[1][i]
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
h.run()
|
|
1287
|
+
h.finitialize(70 * mV)
|
|
1288
|
+
h.continuerun(self.tstop * ms)
|
|
1289
|
+
#h.run()
|
|
1274
1290
|
else:
|
|
1275
|
-
|
|
1291
|
+
# Continuous input mode: ensure simulation runs long enough for the full stimulation duration
|
|
1292
|
+
self.tstop = self.general_settings["tstart"] + self.w_duration.value + 300 # 300ms buffer time
|
|
1276
1293
|
self.nstim.interval = 1000 / input_frequency
|
|
1277
1294
|
self.nstim.number = np.ceil(self.w_duration.value / 1000 * input_frequency + 1)
|
|
1278
1295
|
self.nstim2.number = 0
|
|
1279
|
-
self.tstop = self.w_duration.value + self.general_settings["tstart"]
|
|
1280
1296
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
h.run()
|
|
1297
|
+
h.finitialize(70 * mV)
|
|
1298
|
+
h.continuerun(self.tstop * ms)
|
|
1299
|
+
#h.run()
|
|
1284
1300
|
|
|
1285
1301
|
def InteractiveTuner(self):
|
|
1286
1302
|
"""
|
|
@@ -1375,6 +1391,52 @@ class SynapseTuner:
|
|
|
1375
1391
|
options=durations, value=duration0, description="Duration"
|
|
1376
1392
|
)
|
|
1377
1393
|
|
|
1394
|
+
# Save functionality widgets
|
|
1395
|
+
save_path_text = widgets.Text(
|
|
1396
|
+
value="plot.png",
|
|
1397
|
+
description="Save path:",
|
|
1398
|
+
layout=widgets.Layout(width='300px')
|
|
1399
|
+
)
|
|
1400
|
+
save_button = widgets.Button(description="Save Plot", icon="save", button_style="success")
|
|
1401
|
+
|
|
1402
|
+
def save_plot(b):
|
|
1403
|
+
if hasattr(self, 'last_figure') and self.last_figure is not None:
|
|
1404
|
+
try:
|
|
1405
|
+
# Create a new figure with just the first subplot (synaptic current)
|
|
1406
|
+
fig, ax = plt.subplots(figsize=(8, 6))
|
|
1407
|
+
|
|
1408
|
+
# Get the axes from the original figure
|
|
1409
|
+
original_axes = self.last_figure.get_axes()
|
|
1410
|
+
if len(original_axes) > 0:
|
|
1411
|
+
first_ax = original_axes[0]
|
|
1412
|
+
|
|
1413
|
+
# Copy the data from the first subplot
|
|
1414
|
+
for line in first_ax.get_lines():
|
|
1415
|
+
ax.plot(line.get_xdata(), line.get_ydata(),
|
|
1416
|
+
color=line.get_color(), label=line.get_label())
|
|
1417
|
+
|
|
1418
|
+
# Copy axis labels and title
|
|
1419
|
+
ax.set_xlabel(first_ax.get_xlabel())
|
|
1420
|
+
ax.set_ylabel(first_ax.get_ylabel())
|
|
1421
|
+
ax.set_title(first_ax.get_title())
|
|
1422
|
+
ax.set_xlim(first_ax.get_xlim())
|
|
1423
|
+
ax.legend()
|
|
1424
|
+
ax.grid(True)
|
|
1425
|
+
|
|
1426
|
+
# Save the new figure
|
|
1427
|
+
fig.savefig(save_path_text.value)
|
|
1428
|
+
plt.close(fig) # Close the temporary figure
|
|
1429
|
+
print(f"Synaptic current plot saved to {save_path_text.value}")
|
|
1430
|
+
else:
|
|
1431
|
+
print("No subplots found in the figure")
|
|
1432
|
+
|
|
1433
|
+
except Exception as e:
|
|
1434
|
+
print(f"Error saving plot: {e}")
|
|
1435
|
+
else:
|
|
1436
|
+
print("No plot to save")
|
|
1437
|
+
|
|
1438
|
+
save_button.on_click(save_plot)
|
|
1439
|
+
|
|
1378
1440
|
def create_dynamic_sliders():
|
|
1379
1441
|
"""Create sliders based on current connection's parameters"""
|
|
1380
1442
|
sliders = {}
|
|
@@ -1453,7 +1515,7 @@ class SynapseTuner:
|
|
|
1453
1515
|
the network dropdown. It coordinates the complete switching process:
|
|
1454
1516
|
1. Calls _switch_network() to rebuild connections for the new network
|
|
1455
1517
|
2. Updates the connection dropdown options with new network's connections
|
|
1456
|
-
3. Recreates dynamic sliders for
|
|
1518
|
+
3. Recreates dynamic sliders for new connection parameters
|
|
1457
1519
|
4. Refreshes the entire UI to reflect all changes
|
|
1458
1520
|
"""
|
|
1459
1521
|
if w_network is None:
|
|
@@ -1515,8 +1577,9 @@ class SynapseTuner:
|
|
|
1515
1577
|
else:
|
|
1516
1578
|
connection_row = HBox([w_connection])
|
|
1517
1579
|
slider_row = HBox([w_input_freq, self.w_delay, self.w_duration])
|
|
1580
|
+
save_row = HBox([save_path_text, save_button])
|
|
1518
1581
|
|
|
1519
|
-
ui = VBox([connection_row, button_row, slider_row, slider_columns])
|
|
1582
|
+
ui = VBox([connection_row, button_row, slider_row, slider_columns, save_row])
|
|
1520
1583
|
|
|
1521
1584
|
# Function to update UI based on input mode
|
|
1522
1585
|
def update_ui(*args):
|
|
@@ -1618,6 +1681,7 @@ class SynapseTuner:
|
|
|
1618
1681
|
Dictionary containing frequency-dependent metrics with keys:
|
|
1619
1682
|
- 'frequencies': List of tested frequencies
|
|
1620
1683
|
- 'ppr': Paired-pulse ratios at each frequency
|
|
1684
|
+
- 'simple_ppr': Simple paired-pulse ratios (2nd/1st pulse) at each frequency
|
|
1621
1685
|
- 'induction': Induction values at each frequency
|
|
1622
1686
|
- 'recovery': Recovery values at each frequency
|
|
1623
1687
|
|
|
@@ -1627,7 +1691,7 @@ class SynapseTuner:
|
|
|
1627
1691
|
behavior of synapses, such as identifying facilitating vs. depressing regimes
|
|
1628
1692
|
or the frequency at which a synapse transitions between these behaviors.
|
|
1629
1693
|
"""
|
|
1630
|
-
results = {"frequencies": freqs, "ppr": [], "induction": [], "recovery": []}
|
|
1694
|
+
results = {"frequencies": freqs, "ppr": [], "induction": [], "recovery": [], "simple_ppr": []}
|
|
1631
1695
|
|
|
1632
1696
|
# Store original state
|
|
1633
1697
|
original_ispk = self.ispk
|
|
@@ -1635,11 +1699,12 @@ class SynapseTuner:
|
|
|
1635
1699
|
for freq in tqdm(freqs, desc="Analyzing frequencies"):
|
|
1636
1700
|
self._simulate_model(freq, delay)
|
|
1637
1701
|
amp = self._response_amplitude()
|
|
1638
|
-
ppr, induction, recovery = self._calc_ppr_induction_recovery(amp, print_math=False)
|
|
1702
|
+
ppr, induction, recovery, simple_ppr = self._calc_ppr_induction_recovery(amp, print_math=False)
|
|
1639
1703
|
|
|
1640
1704
|
results["ppr"].append(float(ppr))
|
|
1641
1705
|
results["induction"].append(float(induction))
|
|
1642
1706
|
results["recovery"].append(float(recovery))
|
|
1707
|
+
results["simple_ppr"].append(float(simple_ppr))
|
|
1643
1708
|
|
|
1644
1709
|
# Restore original state
|
|
1645
1710
|
self.ispk = original_ispk
|
|
@@ -1659,6 +1724,7 @@ class SynapseTuner:
|
|
|
1659
1724
|
Dictionary containing frequency analysis results with keys:
|
|
1660
1725
|
- 'frequencies': List of tested frequencies
|
|
1661
1726
|
- 'ppr': Paired-pulse ratios at each frequency
|
|
1727
|
+
- 'simple_ppr': Simple paired-pulse ratios at each frequency
|
|
1662
1728
|
- 'induction': Induction values at each frequency
|
|
1663
1729
|
- 'recovery': Recovery values at each frequency
|
|
1664
1730
|
log_plot : bool
|
|
@@ -1667,24 +1733,27 @@ class SynapseTuner:
|
|
|
1667
1733
|
Notes:
|
|
1668
1734
|
------
|
|
1669
1735
|
Creates a figure with three subplots showing:
|
|
1670
|
-
1. Paired-pulse
|
|
1736
|
+
1. Paired-pulse ratios (both normalized and simple) vs. frequency
|
|
1671
1737
|
2. Induction vs. frequency
|
|
1672
1738
|
3. Recovery vs. frequency
|
|
1673
1739
|
|
|
1674
1740
|
Each plot includes a horizontal reference line at y=0 or y=1 to indicate
|
|
1675
1741
|
the boundary between facilitation and depression.
|
|
1676
1742
|
"""
|
|
1677
|
-
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(
|
|
1743
|
+
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18, 5))
|
|
1678
1744
|
|
|
1679
|
-
# Plot PPR
|
|
1745
|
+
# Plot both PPR measures
|
|
1680
1746
|
if log_plot:
|
|
1681
|
-
ax1.semilogx(results["frequencies"], results["ppr"], "o-")
|
|
1747
|
+
ax1.semilogx(results["frequencies"], results["ppr"], "o-", label="Normalized PPR")
|
|
1748
|
+
ax1.semilogx(results["frequencies"], results["simple_ppr"], "s-", label="Simple PPR")
|
|
1682
1749
|
else:
|
|
1683
|
-
ax1.plot(results["frequencies"], results["ppr"], "o-")
|
|
1750
|
+
ax1.plot(results["frequencies"], results["ppr"], "o-", label="Normalized PPR")
|
|
1751
|
+
ax1.plot(results["frequencies"], results["simple_ppr"], "s-", label="Simple PPR")
|
|
1684
1752
|
ax1.axhline(y=1, color="gray", linestyle="--", alpha=0.5)
|
|
1685
1753
|
ax1.set_xlabel("Frequency (Hz)")
|
|
1686
1754
|
ax1.set_ylabel("Paired Pulse Ratio")
|
|
1687
1755
|
ax1.set_title("PPR vs Frequency")
|
|
1756
|
+
ax1.legend()
|
|
1688
1757
|
ax1.grid(True)
|
|
1689
1758
|
|
|
1690
1759
|
# Plot Induction
|
|
@@ -1713,6 +1782,168 @@ class SynapseTuner:
|
|
|
1713
1782
|
plt.show()
|
|
1714
1783
|
|
|
1715
1784
|
|
|
1785
|
+
def generate_synaptic_table(self, stp_frequency=50.0, stp_delay=250.0, plot=True):
|
|
1786
|
+
"""
|
|
1787
|
+
Generate a comprehensive table of synaptic parameters for all connections.
|
|
1788
|
+
|
|
1789
|
+
This method iterates through all available connections, runs simulations to
|
|
1790
|
+
characterize each synapse, and compiles the results into a pandas DataFrame.
|
|
1791
|
+
|
|
1792
|
+
Parameters:
|
|
1793
|
+
-----------
|
|
1794
|
+
stp_frequency : float, optional
|
|
1795
|
+
Frequency in Hz to use for STP (short-term plasticity) analysis. Default is 50.0 Hz.
|
|
1796
|
+
stp_delay : float, optional
|
|
1797
|
+
Delay in ms between pulse trains for STP analysis. Default is 250.0 ms.
|
|
1798
|
+
plot : bool, optional
|
|
1799
|
+
Whether to display the resulting table. Default is True.
|
|
1800
|
+
|
|
1801
|
+
Returns:
|
|
1802
|
+
--------
|
|
1803
|
+
pd.DataFrame
|
|
1804
|
+
DataFrame containing synaptic parameters for each connection with columns:
|
|
1805
|
+
- connection: Connection name
|
|
1806
|
+
- rise_time: 20-80% rise time (ms)
|
|
1807
|
+
- decay_time: Decay time constant (ms)
|
|
1808
|
+
- latency: Response latency (ms)
|
|
1809
|
+
- half_width: Response half-width (ms)
|
|
1810
|
+
- peak_amplitude: Peak synaptic current amplitude (pA)
|
|
1811
|
+
- baseline: Baseline current (pA)
|
|
1812
|
+
- ppr: Paired-pulse ratio (normalized)
|
|
1813
|
+
- simple_ppr: Simple paired-pulse ratio (2nd/1st pulse)
|
|
1814
|
+
- induction: STP induction measure
|
|
1815
|
+
- recovery: STP recovery measure
|
|
1816
|
+
|
|
1817
|
+
Notes:
|
|
1818
|
+
------
|
|
1819
|
+
This method temporarily switches between connections to characterize each one,
|
|
1820
|
+
then restores the original connection. The STP metrics are calculated at the
|
|
1821
|
+
specified frequency and delay.
|
|
1822
|
+
"""
|
|
1823
|
+
# Store original connection to restore later
|
|
1824
|
+
original_connection = self.current_connection
|
|
1825
|
+
|
|
1826
|
+
# Initialize results list
|
|
1827
|
+
results = []
|
|
1828
|
+
|
|
1829
|
+
print(f"Analyzing {len(self.conn_type_settings)} connections...")
|
|
1830
|
+
|
|
1831
|
+
for conn_name in tqdm(self.conn_type_settings.keys(), desc="Analyzing connections"):
|
|
1832
|
+
try:
|
|
1833
|
+
# Switch to this connection
|
|
1834
|
+
self._switch_connection(conn_name)
|
|
1835
|
+
|
|
1836
|
+
# Run single event analysis
|
|
1837
|
+
self.SingleEvent(plot_and_print=False)
|
|
1838
|
+
|
|
1839
|
+
# Get synaptic properties from the single event
|
|
1840
|
+
syn_props = self._get_syn_prop()
|
|
1841
|
+
|
|
1842
|
+
# Run STP analysis at specified frequency
|
|
1843
|
+
stp_results = self.stp_frequency_response(
|
|
1844
|
+
freqs=[stp_frequency],
|
|
1845
|
+
delay=stp_delay,
|
|
1846
|
+
plot=False,
|
|
1847
|
+
log_plot=False
|
|
1848
|
+
)
|
|
1849
|
+
|
|
1850
|
+
# Extract STP metrics for this frequency
|
|
1851
|
+
freq_idx = 0 # Only one frequency tested
|
|
1852
|
+
ppr = stp_results['ppr'][freq_idx]
|
|
1853
|
+
induction = stp_results['induction'][freq_idx]
|
|
1854
|
+
recovery = stp_results['recovery'][freq_idx]
|
|
1855
|
+
simple_ppr = stp_results['simple_ppr'][freq_idx]
|
|
1856
|
+
|
|
1857
|
+
# Compile results for this connection
|
|
1858
|
+
conn_results = {
|
|
1859
|
+
'connection': conn_name,
|
|
1860
|
+
'rise_time': float(self.rise_time),
|
|
1861
|
+
'decay_time': float(self.decay_time),
|
|
1862
|
+
'latency': float(syn_props.get('latency', 0)),
|
|
1863
|
+
'half_width': float(syn_props.get('half_width', 0)),
|
|
1864
|
+
'peak_amplitude': float(syn_props.get('amp', 0)),
|
|
1865
|
+
'baseline': float(syn_props.get('baseline', 0)),
|
|
1866
|
+
'ppr': float(ppr),
|
|
1867
|
+
'simple_ppr': float(simple_ppr),
|
|
1868
|
+
'induction': float(induction),
|
|
1869
|
+
'recovery': float(recovery)
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
results.append(conn_results)
|
|
1873
|
+
|
|
1874
|
+
except Exception as e:
|
|
1875
|
+
print(f"Warning: Failed to analyze connection '{conn_name}': {e}")
|
|
1876
|
+
# Add partial results if possible
|
|
1877
|
+
results.append({
|
|
1878
|
+
'connection': conn_name,
|
|
1879
|
+
'rise_time': float('nan'),
|
|
1880
|
+
'decay_time': float('nan'),
|
|
1881
|
+
'latency': float('nan'),
|
|
1882
|
+
'half_width': float('nan'),
|
|
1883
|
+
'peak_amplitude': float('nan'),
|
|
1884
|
+
'baseline': float('nan'),
|
|
1885
|
+
'ppr': float('nan'),
|
|
1886
|
+
'simple_ppr': float('nan'),
|
|
1887
|
+
'induction': float('nan'),
|
|
1888
|
+
'recovery': float('nan')
|
|
1889
|
+
})
|
|
1890
|
+
|
|
1891
|
+
# Restore original connection
|
|
1892
|
+
if original_connection in self.conn_type_settings:
|
|
1893
|
+
self._switch_connection(original_connection)
|
|
1894
|
+
|
|
1895
|
+
# Create DataFrame
|
|
1896
|
+
df = pd.DataFrame(results)
|
|
1897
|
+
|
|
1898
|
+
# Set connection as index for better display
|
|
1899
|
+
df = df.set_index('connection')
|
|
1900
|
+
|
|
1901
|
+
if plot:
|
|
1902
|
+
# Display the table
|
|
1903
|
+
print("\nSynaptic Parameters Table:")
|
|
1904
|
+
print("=" * 80)
|
|
1905
|
+
display(df.round(4))
|
|
1906
|
+
|
|
1907
|
+
# Optional: Create a simple bar plot for key metrics
|
|
1908
|
+
try:
|
|
1909
|
+
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
|
1910
|
+
fig.suptitle(f'Synaptic Parameters Across Connections (STP at {stp_frequency}Hz)', fontsize=16)
|
|
1911
|
+
|
|
1912
|
+
# Plot rise/decay times
|
|
1913
|
+
df[['rise_time', 'decay_time']].plot(kind='bar', ax=axes[0,0])
|
|
1914
|
+
axes[0,0].set_title('Rise and Decay Times')
|
|
1915
|
+
axes[0,0].set_ylabel('Time (ms)')
|
|
1916
|
+
axes[0,0].tick_params(axis='x', rotation=45)
|
|
1917
|
+
|
|
1918
|
+
# Plot PPR metrics
|
|
1919
|
+
df[['ppr', 'simple_ppr']].plot(kind='bar', ax=axes[0,1])
|
|
1920
|
+
axes[0,1].set_title('Paired-Pulse Ratios')
|
|
1921
|
+
axes[0,1].axhline(y=1, color='gray', linestyle='--', alpha=0.5)
|
|
1922
|
+
axes[0,1].tick_params(axis='x', rotation=45)
|
|
1923
|
+
|
|
1924
|
+
# Plot induction
|
|
1925
|
+
df['induction'].plot(kind='bar', ax=axes[1,0], color='green')
|
|
1926
|
+
axes[1,0].set_title('STP Induction')
|
|
1927
|
+
axes[1,0].axhline(y=0, color='gray', linestyle='--', alpha=0.5)
|
|
1928
|
+
axes[1,0].set_ylabel('Induction')
|
|
1929
|
+
axes[1,0].tick_params(axis='x', rotation=45)
|
|
1930
|
+
|
|
1931
|
+
# Plot recovery
|
|
1932
|
+
df['recovery'].plot(kind='bar', ax=axes[1,1], color='orange')
|
|
1933
|
+
axes[1,1].set_title('STP Recovery')
|
|
1934
|
+
axes[1,1].axhline(y=0, color='gray', linestyle='--', alpha=0.5)
|
|
1935
|
+
axes[1,1].set_ylabel('Recovery')
|
|
1936
|
+
axes[1,1].tick_params(axis='x', rotation=45)
|
|
1937
|
+
|
|
1938
|
+
plt.tight_layout()
|
|
1939
|
+
plt.show()
|
|
1940
|
+
|
|
1941
|
+
except Exception as e:
|
|
1942
|
+
print(f"Warning: Could not create plots: {e}")
|
|
1943
|
+
|
|
1944
|
+
return df
|
|
1945
|
+
|
|
1946
|
+
|
|
1716
1947
|
class GapJunctionTuner:
|
|
1717
1948
|
def __init__(
|
|
1718
1949
|
self,
|
|
@@ -1767,6 +1998,7 @@ class GapJunctionTuner:
|
|
|
1767
1998
|
self.config = config
|
|
1768
1999
|
self.available_networks = []
|
|
1769
2000
|
self.current_network = None
|
|
2001
|
+
self.last_figure = None
|
|
1770
2002
|
if self.conn_type_settings is None and self.config is not None:
|
|
1771
2003
|
self.conn_type_settings = self._build_conn_type_settings_from_config(self.config)
|
|
1772
2004
|
if self.conn_type_settings is None or len(self.conn_type_settings) == 0:
|
|
@@ -2168,6 +2400,7 @@ class GapJunctionTuner:
|
|
|
2168
2400
|
plt.xlabel("Time (ms)")
|
|
2169
2401
|
plt.ylabel("Membrane Voltage (mV)")
|
|
2170
2402
|
plt.legend()
|
|
2403
|
+
self.last_figure = plt.gcf()
|
|
2171
2404
|
|
|
2172
2405
|
def coupling_coefficient(self, t, v1, v2, t_start, t_end, dt=h.dt):
|
|
2173
2406
|
"""
|
|
@@ -2202,75 +2435,649 @@ class GapJunctionTuner:
|
|
|
2202
2435
|
return (v2[idx2] - v2[idx1]) / (v1[idx2] - v1[idx1])
|
|
2203
2436
|
|
|
2204
2437
|
def InteractiveTuner(self):
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2438
|
+
"""
|
|
2439
|
+
Sets up interactive sliders for tuning short-term plasticity (STP) parameters in a Jupyter Notebook.
|
|
2440
|
+
|
|
2441
|
+
This method creates an interactive UI with sliders for:
|
|
2442
|
+
- Network selection dropdown (if multiple networks available and config provided)
|
|
2443
|
+
- Connection type selection dropdown
|
|
2444
|
+
- Input frequency
|
|
2445
|
+
- Delay between pulse trains
|
|
2446
|
+
- Duration of stimulation (for continuous input mode)
|
|
2447
|
+
- Synaptic parameters (e.g., Use, tau_f, tau_d) based on the syn model
|
|
2448
|
+
|
|
2449
|
+
It also provides buttons for:
|
|
2450
|
+
- Running a single event simulation
|
|
2451
|
+
- Running a train input simulation
|
|
2452
|
+
- Toggling voltage clamp mode
|
|
2453
|
+
- Switching between standard and continuous input modes
|
|
2218
2454
|
|
|
2219
|
-
|
|
2455
|
+
Network Dropdown Feature:
|
|
2456
|
+
------------------------
|
|
2457
|
+
When the SynapseTuner is initialized with a BMTK config file containing multiple networks:
|
|
2458
|
+
- A network dropdown appears next to the connection dropdown
|
|
2459
|
+
- Users can dynamically switch between networks (e.g., 'network_to_network', 'external_to_network')
|
|
2460
|
+
- Switching networks rebuilds available connections and updates the connection dropdown
|
|
2461
|
+
- The current connection is preserved if it exists in the new network
|
|
2462
|
+
- If multiple networks exist but only one is specified during init, that network is used as default
|
|
2220
2463
|
|
|
2221
|
-
|
|
2464
|
+
Notes:
|
|
2465
|
+
------
|
|
2466
|
+
Ideal for exploratory parameter tuning and interactive visualization of
|
|
2467
|
+
synapse behavior with different parameter values and stimulation protocols.
|
|
2468
|
+
The network dropdown feature enables comprehensive exploration of multi-network
|
|
2469
|
+
BMTK simulations without needing to reinitialize the tuner.
|
|
2470
|
+
"""
|
|
2471
|
+
# Widgets setup (Sliders)
|
|
2472
|
+
freqs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 35, 50, 100, 200]
|
|
2473
|
+
delays = [125, 250, 500, 1000, 2000, 4000]
|
|
2474
|
+
durations = [100, 300, 500, 1000, 2000, 5000, 10000]
|
|
2475
|
+
freq0 = 50
|
|
2476
|
+
delay0 = 250
|
|
2477
|
+
duration0 = 300
|
|
2478
|
+
vlamp_status = self.vclamp
|
|
2222
2479
|
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2480
|
+
# Connection dropdown
|
|
2481
|
+
connection_options = sorted(list(self.conn_type_settings.keys()))
|
|
2482
|
+
w_connection = widgets.Dropdown(
|
|
2483
|
+
options=connection_options,
|
|
2484
|
+
value=self.current_connection,
|
|
2485
|
+
description="Connection:",
|
|
2486
|
+
style={'description_width': 'initial'}
|
|
2487
|
+
)
|
|
2227
2488
|
|
|
2228
|
-
|
|
2229
|
-
|
|
2489
|
+
# Network dropdown - only shown if config was provided and multiple networks are available
|
|
2490
|
+
# This enables users to switch between different network datasets dynamically
|
|
2491
|
+
w_network = None
|
|
2492
|
+
if self.config is not None and len(self.available_networks) > 1:
|
|
2493
|
+
w_network = widgets.Dropdown(
|
|
2494
|
+
options=self.available_networks,
|
|
2495
|
+
value=self.current_network,
|
|
2496
|
+
description="Network:",
|
|
2497
|
+
style={'description_width': 'initial'}
|
|
2498
|
+
)
|
|
2230
2499
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2500
|
+
w_run = widgets.Button(description="Run Train", icon="history", button_style="primary")
|
|
2501
|
+
w_single = widgets.Button(description="Single Event", icon="check", button_style="success")
|
|
2502
|
+
w_vclamp = widgets.ToggleButton(
|
|
2503
|
+
value=vlamp_status,
|
|
2504
|
+
description="Voltage Clamp",
|
|
2505
|
+
icon="fast-backward",
|
|
2506
|
+
button_style="warning",
|
|
2507
|
+
)
|
|
2508
|
+
|
|
2509
|
+
# Voltage clamp amplitude input
|
|
2510
|
+
default_vclamp_amp = getattr(self.conn['spec_settings'], 'vclamp_amp', -70.0)
|
|
2511
|
+
w_vclamp_amp = widgets.FloatText(
|
|
2512
|
+
value=default_vclamp_amp,
|
|
2513
|
+
description="V_clamp (mV):",
|
|
2514
|
+
step=5.0,
|
|
2515
|
+
style={'description_width': 'initial'},
|
|
2516
|
+
layout=widgets.Layout(width='150px')
|
|
2517
|
+
)
|
|
2518
|
+
|
|
2519
|
+
w_input_mode = widgets.ToggleButton(
|
|
2520
|
+
value=False, description="Continuous input", icon="eject", button_style="info"
|
|
2521
|
+
)
|
|
2522
|
+
w_input_freq = widgets.SelectionSlider(options=freqs, value=freq0, description="Input Freq")
|
|
2234
2523
|
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2524
|
+
# Sliders for delay and duration
|
|
2525
|
+
self.w_delay = widgets.SelectionSlider(options=delays, value=delay0, description="Delay")
|
|
2526
|
+
self.w_duration = widgets.SelectionSlider(
|
|
2527
|
+
options=durations, value=duration0, description="Duration"
|
|
2528
|
+
)
|
|
2239
2529
|
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2530
|
+
# Save functionality widgets
|
|
2531
|
+
save_path_text = widgets.Text(
|
|
2532
|
+
value="plot.png",
|
|
2533
|
+
description="Save path:",
|
|
2534
|
+
layout=widgets.Layout(width='300px')
|
|
2535
|
+
)
|
|
2536
|
+
save_button = widgets.Button(description="Save Plot", icon="save", button_style="success")
|
|
2243
2537
|
|
|
2538
|
+
def save_plot(b):
|
|
2539
|
+
if hasattr(self, 'last_figure') and self.last_figure is not None:
|
|
2540
|
+
try:
|
|
2541
|
+
# Create a new figure with just the first subplot (synaptic current)
|
|
2542
|
+
fig, ax = plt.subplots(figsize=(8, 6))
|
|
2543
|
+
|
|
2544
|
+
# Get the axes from the original figure
|
|
2545
|
+
original_axes = self.last_figure.get_axes()
|
|
2546
|
+
if len(original_axes) > 0:
|
|
2547
|
+
first_ax = original_axes[0]
|
|
2548
|
+
|
|
2549
|
+
# Copy the data from the first subplot
|
|
2550
|
+
for line in first_ax.get_lines():
|
|
2551
|
+
ax.plot(line.get_xdata(), line.get_ydata(),
|
|
2552
|
+
color=line.get_color(), label=line.get_label())
|
|
2553
|
+
|
|
2554
|
+
# Copy axis labels and title
|
|
2555
|
+
ax.set_xlabel(first_ax.get_xlabel())
|
|
2556
|
+
ax.set_ylabel(first_ax.get_ylabel())
|
|
2557
|
+
ax.set_title(first_ax.get_title())
|
|
2558
|
+
ax.set_xlim(first_ax.get_xlim())
|
|
2559
|
+
ax.legend()
|
|
2560
|
+
ax.grid(True)
|
|
2561
|
+
|
|
2562
|
+
# Save the new figure
|
|
2563
|
+
fig.savefig(save_path_text.value)
|
|
2564
|
+
plt.close(fig) # Close the temporary figure
|
|
2565
|
+
print(f"Synaptic current plot saved to {save_path_text.value}")
|
|
2566
|
+
else:
|
|
2567
|
+
print("No subplots found in the figure")
|
|
2568
|
+
|
|
2244
2569
|
except Exception as e:
|
|
2245
|
-
print(f"Error
|
|
2246
|
-
|
|
2570
|
+
print(f"Error saving plot: {e}")
|
|
2571
|
+
else:
|
|
2572
|
+
print("No plot to save")
|
|
2247
2573
|
|
|
2248
|
-
|
|
2574
|
+
save_button.on_click(save_plot)
|
|
2249
2575
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
value
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2576
|
+
def create_dynamic_sliders():
|
|
2577
|
+
"""Create sliders based on current connection's parameters"""
|
|
2578
|
+
sliders = {}
|
|
2579
|
+
for key, value in self.slider_vars.items():
|
|
2580
|
+
if isinstance(value, (int, float)): # Only create sliders for numeric values
|
|
2581
|
+
if hasattr(self.syn, key):
|
|
2582
|
+
if value == 0:
|
|
2583
|
+
print(
|
|
2584
|
+
f"{key} was set to zero, going to try to set a range of values, try settings the {key} to a nonzero value if you dont like the range!"
|
|
2585
|
+
)
|
|
2586
|
+
slider = widgets.FloatSlider(
|
|
2587
|
+
value=value, min=0, max=1000, step=1, description=key
|
|
2588
|
+
)
|
|
2589
|
+
else:
|
|
2590
|
+
slider = widgets.FloatSlider(
|
|
2591
|
+
value=value, min=0, max=value * 20, step=value / 5, description=key
|
|
2592
|
+
)
|
|
2593
|
+
sliders[key] = slider
|
|
2594
|
+
else:
|
|
2595
|
+
print(f"skipping slider for {key} due to not being a synaptic variable")
|
|
2596
|
+
return sliders
|
|
2263
2597
|
|
|
2264
|
-
|
|
2598
|
+
# Generate sliders dynamically based on valid numeric entries in self.slider_vars
|
|
2599
|
+
self.dynamic_sliders = create_dynamic_sliders()
|
|
2600
|
+
print(
|
|
2601
|
+
"Setting up slider! The sliders ranges are set by their init value so try changing that if you dont like the slider range!"
|
|
2602
|
+
)
|
|
2265
2603
|
|
|
2266
|
-
|
|
2267
|
-
|
|
2604
|
+
# Create output widget for displaying results
|
|
2605
|
+
output_widget = widgets.Output()
|
|
2606
|
+
|
|
2607
|
+
def run_single_event(*args):
|
|
2608
|
+
clear_output()
|
|
2609
|
+
display(ui)
|
|
2610
|
+
display(output_widget)
|
|
2611
|
+
|
|
2612
|
+
self.vclamp = w_vclamp.value
|
|
2613
|
+
# Update voltage clamp amplitude if voltage clamp is enabled
|
|
2614
|
+
if self.vclamp:
|
|
2615
|
+
# Update the voltage clamp amplitude settings
|
|
2616
|
+
self.conn['spec_settings']['vclamp_amp'] = w_vclamp_amp.value
|
|
2617
|
+
# Update general settings if they exist
|
|
2618
|
+
if hasattr(self, 'general_settings'):
|
|
2619
|
+
self.general_settings['vclamp_amp'] = w_vclamp_amp.value
|
|
2620
|
+
# Update synaptic properties based on slider values
|
|
2621
|
+
self.ispk = None
|
|
2622
|
+
|
|
2623
|
+
# Clear previous results and run simulation
|
|
2624
|
+
output_widget.clear_output()
|
|
2625
|
+
with output_widget:
|
|
2626
|
+
self.SingleEvent()
|
|
2268
2627
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2628
|
+
def on_connection_change(*args):
|
|
2629
|
+
"""Handle connection dropdown change"""
|
|
2630
|
+
try:
|
|
2631
|
+
new_connection = w_connection.value
|
|
2632
|
+
if new_connection != self.current_connection:
|
|
2633
|
+
# Switch to new connection
|
|
2634
|
+
self._switch_connection(new_connection)
|
|
2635
|
+
|
|
2636
|
+
# Recreate dynamic sliders for new connection
|
|
2637
|
+
self.dynamic_sliders = create_dynamic_sliders()
|
|
2638
|
+
|
|
2639
|
+
# Update UI
|
|
2640
|
+
update_ui_layout()
|
|
2641
|
+
update_ui()
|
|
2642
|
+
|
|
2643
|
+
except Exception as e:
|
|
2644
|
+
print(f"Error switching connection: {e}")
|
|
2272
2645
|
|
|
2646
|
+
def on_network_change(*args):
|
|
2647
|
+
"""
|
|
2648
|
+
Handle network dropdown change events.
|
|
2649
|
+
|
|
2650
|
+
This callback is triggered when the user selects a different network from
|
|
2651
|
+
the network dropdown. It coordinates the complete switching process:
|
|
2652
|
+
1. Calls _switch_network() to rebuild connections for the new network
|
|
2653
|
+
2. Updates the connection dropdown options with new network's connections
|
|
2654
|
+
3. Recreates dynamic sliders for new connection parameters
|
|
2655
|
+
4. Refreshes the entire UI to reflect all changes
|
|
2656
|
+
"""
|
|
2657
|
+
if w_network is None:
|
|
2658
|
+
return
|
|
2659
|
+
try:
|
|
2660
|
+
new_network = w_network.value
|
|
2661
|
+
if new_network != self.current_network:
|
|
2662
|
+
# Switch to new network
|
|
2663
|
+
self._switch_network(new_network)
|
|
2664
|
+
|
|
2665
|
+
# Update connection dropdown options with new network's connections
|
|
2666
|
+
connection_options = list(self.conn_type_settings.keys())
|
|
2667
|
+
w_connection.options = connection_options
|
|
2668
|
+
if connection_options:
|
|
2669
|
+
w_connection.value = self.current_connection
|
|
2670
|
+
|
|
2671
|
+
# Recreate dynamic sliders for new connection
|
|
2672
|
+
self.dynamic_sliders = create_dynamic_sliders()
|
|
2673
|
+
|
|
2674
|
+
# Update UI
|
|
2675
|
+
update_ui_layout()
|
|
2676
|
+
update_ui()
|
|
2677
|
+
|
|
2678
|
+
except Exception as e:
|
|
2679
|
+
print(f"Error switching network: {e}")
|
|
2273
2680
|
|
|
2681
|
+
def update_ui_layout():
|
|
2682
|
+
"""
|
|
2683
|
+
Update the UI layout with new sliders and network dropdown.
|
|
2684
|
+
|
|
2685
|
+
This function reconstructs the entire UI layout including:
|
|
2686
|
+
- Network dropdown (if available) and connection dropdown in the top row
|
|
2687
|
+
- Button controls and input mode toggles
|
|
2688
|
+
- Parameter sliders arranged in columns
|
|
2689
|
+
"""
|
|
2690
|
+
nonlocal ui, slider_columns
|
|
2691
|
+
|
|
2692
|
+
# Add the dynamic sliders to the UI
|
|
2693
|
+
slider_widgets = [slider for slider in self.dynamic_sliders.values()]
|
|
2694
|
+
|
|
2695
|
+
if slider_widgets:
|
|
2696
|
+
half = len(slider_widgets) // 2
|
|
2697
|
+
col1 = VBox(slider_widgets[:half])
|
|
2698
|
+
col2 = VBox(slider_widgets[half:])
|
|
2699
|
+
slider_columns = HBox([col1, col2])
|
|
2700
|
+
else:
|
|
2701
|
+
slider_columns = VBox([])
|
|
2702
|
+
|
|
2703
|
+
# Create button row with voltage clamp controls
|
|
2704
|
+
if w_vclamp.value: # Show voltage clamp amplitude input when toggle is on
|
|
2705
|
+
button_row = HBox([w_run, w_single, w_vclamp, w_vclamp_amp, w_input_mode])
|
|
2706
|
+
else: # Hide voltage clamp amplitude input when toggle is off
|
|
2707
|
+
button_row = HBox([w_run, w_single, w_vclamp, w_input_mode])
|
|
2708
|
+
|
|
2709
|
+
# Construct the top row - include network dropdown if available
|
|
2710
|
+
# This creates a horizontal layout with network dropdown (if present) and connection dropdown
|
|
2711
|
+
if w_network is not None:
|
|
2712
|
+
connection_row = HBox([w_network, w_connection])
|
|
2713
|
+
else:
|
|
2714
|
+
connection_row = HBox([w_connection])
|
|
2715
|
+
slider_row = HBox([w_input_freq, self.w_delay, self.w_duration])
|
|
2716
|
+
save_row = HBox([save_path_text, save_button])
|
|
2717
|
+
|
|
2718
|
+
ui = VBox([connection_row, button_row, slider_row, slider_columns, save_row])
|
|
2719
|
+
|
|
2720
|
+
# Function to update UI based on input mode
|
|
2721
|
+
def update_ui(*args):
|
|
2722
|
+
clear_output()
|
|
2723
|
+
display(ui)
|
|
2724
|
+
display(output_widget)
|
|
2725
|
+
|
|
2726
|
+
self.vclamp = w_vclamp.value
|
|
2727
|
+
# Update voltage clamp amplitude if voltage clamp is enabled
|
|
2728
|
+
if self.vclamp:
|
|
2729
|
+
self.conn['spec_settings']['vclamp_amp'] = w_vclamp_amp.value
|
|
2730
|
+
if hasattr(self, 'general_settings'):
|
|
2731
|
+
self.general_settings['vclamp_amp'] = w_vclamp_amp.value
|
|
2732
|
+
|
|
2733
|
+
self.input_mode = w_input_mode.value
|
|
2734
|
+
syn_props = {var: slider.value for var, slider in self.dynamic_sliders.items()}
|
|
2735
|
+
self._set_syn_prop(**syn_props)
|
|
2736
|
+
|
|
2737
|
+
# Clear previous results and run simulation
|
|
2738
|
+
output_widget.clear_output()
|
|
2739
|
+
with output_widget:
|
|
2740
|
+
if not self.input_mode:
|
|
2741
|
+
self._simulate_model(w_input_freq.value, self.w_delay.value, w_vclamp.value)
|
|
2742
|
+
else:
|
|
2743
|
+
self._simulate_model(w_input_freq.value, self.w_duration.value, w_vclamp.value)
|
|
2744
|
+
amp = self._response_amplitude()
|
|
2745
|
+
self._plot_model(
|
|
2746
|
+
[self.general_settings["tstart"] - self.nstim.interval / 3, self.tstop]
|
|
2747
|
+
)
|
|
2748
|
+
_ = self._calc_ppr_induction_recovery(amp)
|
|
2749
|
+
|
|
2750
|
+
# Function to switch between delay and duration sliders
|
|
2751
|
+
def switch_slider(*args):
|
|
2752
|
+
if w_input_mode.value:
|
|
2753
|
+
self.w_delay.layout.display = "none" # Hide delay slider
|
|
2754
|
+
self.w_duration.layout.display = "" # Show duration slider
|
|
2755
|
+
else:
|
|
2756
|
+
self.w_delay.layout.display = "" # Show delay slider
|
|
2757
|
+
self.w_duration.layout.display = "none" # Hide duration slider
|
|
2758
|
+
|
|
2759
|
+
# Function to handle voltage clamp toggle
|
|
2760
|
+
def on_vclamp_toggle(*args):
|
|
2761
|
+
"""Handle voltage clamp toggle changes to show/hide amplitude input"""
|
|
2762
|
+
update_ui_layout()
|
|
2763
|
+
clear_output()
|
|
2764
|
+
display(ui)
|
|
2765
|
+
display(output_widget)
|
|
2766
|
+
|
|
2767
|
+
# Link widgets to their callback functions
|
|
2768
|
+
w_connection.observe(on_connection_change, names="value")
|
|
2769
|
+
# Link network dropdown callback only if network dropdown was created
|
|
2770
|
+
if w_network is not None:
|
|
2771
|
+
w_network.observe(on_network_change, names="value")
|
|
2772
|
+
w_input_mode.observe(switch_slider, names="value")
|
|
2773
|
+
w_vclamp.observe(on_vclamp_toggle, names="value")
|
|
2774
|
+
|
|
2775
|
+
# Hide the duration slider initially until the user selects it
|
|
2776
|
+
self.w_duration.layout.display = "none" # Hide duration slider
|
|
2777
|
+
|
|
2778
|
+
w_single.on_click(run_single_event)
|
|
2779
|
+
w_run.on_click(update_ui)
|
|
2780
|
+
|
|
2781
|
+
# Initial UI setup
|
|
2782
|
+
slider_columns = VBox([])
|
|
2783
|
+
ui = VBox([])
|
|
2784
|
+
update_ui_layout()
|
|
2785
|
+
|
|
2786
|
+
display(ui)
|
|
2787
|
+
update_ui()
|
|
2788
|
+
|
|
2789
|
+
def stp_frequency_response(
|
|
2790
|
+
self,
|
|
2791
|
+
freqs=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 35, 50, 100, 200],
|
|
2792
|
+
delay=250,
|
|
2793
|
+
plot=True,
|
|
2794
|
+
log_plot=True,
|
|
2795
|
+
):
|
|
2796
|
+
"""
|
|
2797
|
+
Analyze synaptic response across different stimulation frequencies.
|
|
2798
|
+
|
|
2799
|
+
This method systematically tests how the synapse model responds to different
|
|
2800
|
+
stimulation frequencies, calculating key short-term plasticity (STP) metrics
|
|
2801
|
+
for each frequency.
|
|
2802
|
+
|
|
2803
|
+
Parameters:
|
|
2804
|
+
-----------
|
|
2805
|
+
freqs : list, optional
|
|
2806
|
+
List of frequencies to analyze (in Hz). Default covers a wide range from 1-200 Hz.
|
|
2807
|
+
delay : float, optional
|
|
2808
|
+
Delay between pulse trains in ms. Default is 250 ms.
|
|
2809
|
+
plot : bool, optional
|
|
2810
|
+
Whether to plot the results. Default is True.
|
|
2811
|
+
log_plot : bool, optional
|
|
2812
|
+
Whether to use logarithmic scale for frequency axis. Default is True.
|
|
2813
|
+
|
|
2814
|
+
Returns:
|
|
2815
|
+
--------
|
|
2816
|
+
dict
|
|
2817
|
+
Dictionary containing frequency-dependent metrics with keys:
|
|
2818
|
+
- 'frequencies': List of tested frequencies
|
|
2819
|
+
- 'ppr': Paired-pulse ratios at each frequency
|
|
2820
|
+
- 'simple_ppr': Simple paired-pulse ratios (2nd/1st pulse) at each frequency
|
|
2821
|
+
- 'induction': Induction values at each frequency
|
|
2822
|
+
- 'recovery': Recovery values at each frequency
|
|
2823
|
+
|
|
2824
|
+
Notes:
|
|
2825
|
+
------
|
|
2826
|
+
This method is particularly useful for characterizing the frequency-dependent
|
|
2827
|
+
behavior of synapses, such as identifying facilitating vs. depressing regimes
|
|
2828
|
+
or the frequency at which a synapse transitions between these behaviors.
|
|
2829
|
+
"""
|
|
2830
|
+
results = {"frequencies": freqs, "ppr": [], "induction": [], "recovery": [], "simple_ppr": []}
|
|
2831
|
+
|
|
2832
|
+
# Store original state
|
|
2833
|
+
original_ispk = self.ispk
|
|
2834
|
+
|
|
2835
|
+
for freq in tqdm(freqs, desc="Analyzing frequencies"):
|
|
2836
|
+
self._simulate_model(freq, delay)
|
|
2837
|
+
amp = self._response_amplitude()
|
|
2838
|
+
ppr, induction, recovery, simple_ppr = self._calc_ppr_induction_recovery(amp, print_math=False)
|
|
2839
|
+
|
|
2840
|
+
results["ppr"].append(float(ppr))
|
|
2841
|
+
results["induction"].append(float(induction))
|
|
2842
|
+
results["recovery"].append(float(recovery))
|
|
2843
|
+
results["simple_ppr"].append(float(simple_ppr))
|
|
2844
|
+
|
|
2845
|
+
# Restore original state
|
|
2846
|
+
self.ispk = original_ispk
|
|
2847
|
+
|
|
2848
|
+
if plot:
|
|
2849
|
+
self._plot_frequency_analysis(results, log_plot=log_plot)
|
|
2850
|
+
|
|
2851
|
+
return results
|
|
2852
|
+
|
|
2853
|
+
def _plot_frequency_analysis(self, results, log_plot):
|
|
2854
|
+
"""
|
|
2855
|
+
Plot the frequency-dependent synaptic properties.
|
|
2856
|
+
|
|
2857
|
+
Parameters:
|
|
2858
|
+
-----------
|
|
2859
|
+
results : dict
|
|
2860
|
+
Dictionary containing frequency analysis results with keys:
|
|
2861
|
+
- 'frequencies': List of tested frequencies
|
|
2862
|
+
- 'ppr': Paired-pulse ratios at each frequency
|
|
2863
|
+
- 'simple_ppr': Simple paired-pulse ratios at each frequency
|
|
2864
|
+
- 'induction': Induction values at each frequency
|
|
2865
|
+
- 'recovery': Recovery values at each frequency
|
|
2866
|
+
log_plot : bool
|
|
2867
|
+
Whether to use logarithmic scale for frequency axis
|
|
2868
|
+
|
|
2869
|
+
Notes:
|
|
2870
|
+
------
|
|
2871
|
+
Creates a figure with three subplots showing:
|
|
2872
|
+
1. Paired-pulse ratios (both normalized and simple) vs. frequency
|
|
2873
|
+
2. Induction vs. frequency
|
|
2874
|
+
3. Recovery vs. frequency
|
|
2875
|
+
|
|
2876
|
+
Each plot includes a horizontal reference line at y=0 or y=1 to indicate
|
|
2877
|
+
the boundary between facilitation and depression.
|
|
2878
|
+
"""
|
|
2879
|
+
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18, 5))
|
|
2880
|
+
|
|
2881
|
+
# Plot both PPR measures
|
|
2882
|
+
if log_plot:
|
|
2883
|
+
ax1.semilogx(results["frequencies"], results["ppr"], "o-", label="Normalized PPR")
|
|
2884
|
+
ax1.semilogx(results["frequencies"], results["simple_ppr"], "s-", label="Simple PPR")
|
|
2885
|
+
else:
|
|
2886
|
+
ax1.plot(results["frequencies"], results["ppr"], "o-", label="Normalized PPR")
|
|
2887
|
+
ax1.plot(results["frequencies"], results["simple_ppr"], "s-", label="Simple PPR")
|
|
2888
|
+
ax1.axhline(y=1, color="gray", linestyle="--", alpha=0.5)
|
|
2889
|
+
ax1.set_xlabel("Frequency (Hz)")
|
|
2890
|
+
ax1.set_ylabel("Paired Pulse Ratio")
|
|
2891
|
+
ax1.set_title("PPR vs Frequency")
|
|
2892
|
+
ax1.legend()
|
|
2893
|
+
ax1.grid(True)
|
|
2894
|
+
|
|
2895
|
+
# Plot Induction
|
|
2896
|
+
if log_plot:
|
|
2897
|
+
ax2.semilogx(results["frequencies"], results["induction"], "o-")
|
|
2898
|
+
else:
|
|
2899
|
+
ax2.plot(results["frequencies"], results["induction"], "o-")
|
|
2900
|
+
ax2.axhline(y=0, color="gray", linestyle="--", alpha=0.5)
|
|
2901
|
+
ax2.set_xlabel("Frequency (Hz)")
|
|
2902
|
+
ax2.set_ylabel("Induction")
|
|
2903
|
+
ax2.set_title("Induction vs Frequency")
|
|
2904
|
+
ax2.grid(True)
|
|
2905
|
+
|
|
2906
|
+
# Plot Recovery
|
|
2907
|
+
if log_plot:
|
|
2908
|
+
ax3.semilogx(results["frequencies"], results["recovery"], "o-")
|
|
2909
|
+
else:
|
|
2910
|
+
ax3.plot(results["frequencies"], results["recovery"], "o-")
|
|
2911
|
+
ax3.axhline(y=0, color="gray", linestyle="--", alpha=0.5)
|
|
2912
|
+
ax3.set_xlabel("Frequency (Hz)")
|
|
2913
|
+
ax3.set_ylabel("Recovery")
|
|
2914
|
+
ax3.set_title("Recovery vs Frequency")
|
|
2915
|
+
ax3.grid(True)
|
|
2916
|
+
|
|
2917
|
+
plt.tight_layout()
|
|
2918
|
+
plt.show()
|
|
2919
|
+
|
|
2920
|
+
def generate_synaptic_table(self, stp_frequency=50.0, stp_delay=250.0, plot=True):
|
|
2921
|
+
"""
|
|
2922
|
+
Generate a comprehensive table of synaptic parameters for all connections.
|
|
2923
|
+
|
|
2924
|
+
This method iterates through all available connections, runs simulations to
|
|
2925
|
+
characterize each synapse, and compiles the results into a pandas DataFrame.
|
|
2926
|
+
|
|
2927
|
+
Parameters:
|
|
2928
|
+
-----------
|
|
2929
|
+
stp_frequency : float, optional
|
|
2930
|
+
Frequency in Hz to use for STP (short-term plasticity) analysis. Default is 50.0 Hz.
|
|
2931
|
+
stp_delay : float, optional
|
|
2932
|
+
Delay in ms between pulse trains for STP analysis. Default is 250.0 ms.
|
|
2933
|
+
plot : bool, optional
|
|
2934
|
+
Whether to display the resulting table. Default is True.
|
|
2935
|
+
|
|
2936
|
+
Returns:
|
|
2937
|
+
--------
|
|
2938
|
+
pd.DataFrame
|
|
2939
|
+
DataFrame containing synaptic parameters for each connection with columns:
|
|
2940
|
+
- connection: Connection name
|
|
2941
|
+
- rise_time: 20-80% rise time (ms)
|
|
2942
|
+
- decay_time: Decay time constant (ms)
|
|
2943
|
+
- latency: Response latency (ms)
|
|
2944
|
+
- half_width: Response half-width (ms)
|
|
2945
|
+
- peak_amplitude: Peak synaptic current amplitude (pA)
|
|
2946
|
+
- baseline: Baseline current (pA)
|
|
2947
|
+
- ppr: Paired-pulse ratio (normalized)
|
|
2948
|
+
- simple_ppr: Simple paired-pulse ratio (2nd/1st pulse)
|
|
2949
|
+
- induction: STP induction measure
|
|
2950
|
+
- recovery: STP recovery measure
|
|
2951
|
+
|
|
2952
|
+
Notes:
|
|
2953
|
+
------
|
|
2954
|
+
This method temporarily switches between connections to characterize each one,
|
|
2955
|
+
then restores the original connection. The STP metrics are calculated at the
|
|
2956
|
+
specified frequency and delay.
|
|
2957
|
+
"""
|
|
2958
|
+
# Store original connection to restore later
|
|
2959
|
+
original_connection = self.current_connection
|
|
2960
|
+
|
|
2961
|
+
# Initialize results list
|
|
2962
|
+
results = []
|
|
2963
|
+
|
|
2964
|
+
print(f"Analyzing {len(self.conn_type_settings)} connections...")
|
|
2965
|
+
|
|
2966
|
+
for conn_name in tqdm(self.conn_type_settings.keys(), desc="Analyzing connections"):
|
|
2967
|
+
try:
|
|
2968
|
+
# Switch to this connection
|
|
2969
|
+
self._switch_connection(conn_name)
|
|
2970
|
+
|
|
2971
|
+
# Run single event analysis
|
|
2972
|
+
self.SingleEvent(plot_and_print=False)
|
|
2973
|
+
|
|
2974
|
+
# Get synaptic properties from the single event
|
|
2975
|
+
syn_props = self._get_syn_prop()
|
|
2976
|
+
|
|
2977
|
+
# Run STP analysis at specified frequency
|
|
2978
|
+
stp_results = self.stp_frequency_response(
|
|
2979
|
+
freqs=[stp_frequency],
|
|
2980
|
+
delay=stp_delay,
|
|
2981
|
+
plot=False,
|
|
2982
|
+
log_plot=False
|
|
2983
|
+
)
|
|
2984
|
+
|
|
2985
|
+
# Extract STP metrics for this frequency
|
|
2986
|
+
freq_idx = 0 # Only one frequency tested
|
|
2987
|
+
ppr = stp_results['ppr'][freq_idx]
|
|
2988
|
+
induction = stp_results['induction'][freq_idx]
|
|
2989
|
+
recovery = stp_results['recovery'][freq_idx]
|
|
2990
|
+
simple_ppr = stp_results['simple_ppr'][freq_idx]
|
|
2991
|
+
|
|
2992
|
+
# Compile results for this connection
|
|
2993
|
+
conn_results = {
|
|
2994
|
+
'connection': conn_name,
|
|
2995
|
+
'rise_time': float(self.rise_time),
|
|
2996
|
+
'decay_time': float(self.decay_time),
|
|
2997
|
+
'latency': float(syn_props.get('latency', 0)),
|
|
2998
|
+
'half_width': float(syn_props.get('half_width', 0)),
|
|
2999
|
+
'peak_amplitude': float(syn_props.get('amp', 0)),
|
|
3000
|
+
'baseline': float(syn_props.get('baseline', 0)),
|
|
3001
|
+
'ppr': float(ppr),
|
|
3002
|
+
'simple_ppr': float(simple_ppr),
|
|
3003
|
+
'induction': float(induction),
|
|
3004
|
+
'recovery': float(recovery)
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
results.append(conn_results)
|
|
3008
|
+
|
|
3009
|
+
except Exception as e:
|
|
3010
|
+
print(f"Warning: Failed to analyze connection '{conn_name}': {e}")
|
|
3011
|
+
# Add partial results if possible
|
|
3012
|
+
results.append({
|
|
3013
|
+
'connection': conn_name,
|
|
3014
|
+
'rise_time': float('nan'),
|
|
3015
|
+
'decay_time': float('nan'),
|
|
3016
|
+
'latency': float('nan'),
|
|
3017
|
+
'half_width': float('nan'),
|
|
3018
|
+
'peak_amplitude': float('nan'),
|
|
3019
|
+
'baseline': float('nan'),
|
|
3020
|
+
'ppr': float('nan'),
|
|
3021
|
+
'simple_ppr': float('nan'),
|
|
3022
|
+
'induction': float('nan'),
|
|
3023
|
+
'recovery': float('nan')
|
|
3024
|
+
})
|
|
3025
|
+
|
|
3026
|
+
# Restore original connection
|
|
3027
|
+
if original_connection in self.conn_type_settings:
|
|
3028
|
+
self._switch_connection(original_connection)
|
|
3029
|
+
|
|
3030
|
+
# Create DataFrame
|
|
3031
|
+
df = pd.DataFrame(results)
|
|
3032
|
+
|
|
3033
|
+
# Set connection as index for better display
|
|
3034
|
+
df = df.set_index('connection')
|
|
3035
|
+
|
|
3036
|
+
if plot:
|
|
3037
|
+
# Display the table
|
|
3038
|
+
print("\nSynaptic Parameters Table:")
|
|
3039
|
+
print("=" * 80)
|
|
3040
|
+
display(df.round(4))
|
|
3041
|
+
|
|
3042
|
+
# Optional: Create a simple bar plot for key metrics
|
|
3043
|
+
try:
|
|
3044
|
+
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
|
3045
|
+
fig.suptitle(f'Synaptic Parameters Across Connections (STP at {stp_frequency}Hz)', fontsize=16)
|
|
3046
|
+
|
|
3047
|
+
# Plot rise/decay times
|
|
3048
|
+
df[['rise_time', 'decay_time']].plot(kind='bar', ax=axes[0,0])
|
|
3049
|
+
axes[0,0].set_title('Rise and Decay Times')
|
|
3050
|
+
axes[0,0].set_ylabel('Time (ms)')
|
|
3051
|
+
axes[0,0].tick_params(axis='x', rotation=45)
|
|
3052
|
+
|
|
3053
|
+
# Plot PPR metrics
|
|
3054
|
+
df[['ppr', 'simple_ppr']].plot(kind='bar', ax=axes[0,1])
|
|
3055
|
+
axes[0,1].set_title('Paired-Pulse Ratios')
|
|
3056
|
+
axes[0,1].axhline(y=1, color='gray', linestyle='--', alpha=0.5)
|
|
3057
|
+
axes[0,1].tick_params(axis='x', rotation=45)
|
|
3058
|
+
|
|
3059
|
+
# Plot induction
|
|
3060
|
+
df['induction'].plot(kind='bar', ax=axes[1,0], color='green')
|
|
3061
|
+
axes[1,0].set_title('STP Induction')
|
|
3062
|
+
axes[1,0].axhline(y=0, color='gray', linestyle='--', alpha=0.5)
|
|
3063
|
+
axes[1,0].set_ylabel('Induction')
|
|
3064
|
+
axes[1,0].tick_params(axis='x', rotation=45)
|
|
3065
|
+
|
|
3066
|
+
# Plot recovery
|
|
3067
|
+
df['recovery'].plot(kind='bar', ax=axes[1,1], color='orange')
|
|
3068
|
+
axes[1,1].set_title('STP Recovery')
|
|
3069
|
+
axes[1,1].axhline(y=0, color='gray', linestyle='--', alpha=0.5)
|
|
3070
|
+
axes[1,1].set_ylabel('Recovery')
|
|
3071
|
+
axes[1,1].tick_params(axis='x', rotation=45)
|
|
3072
|
+
|
|
3073
|
+
plt.tight_layout()
|
|
3074
|
+
plt.show()
|
|
3075
|
+
|
|
3076
|
+
except Exception as e:
|
|
3077
|
+
print(f"Warning: Could not create plots: {e}")
|
|
3078
|
+
|
|
3079
|
+
return df
|
|
3080
|
+
|
|
2274
3081
|
# optimizers!
|
|
2275
3082
|
|
|
2276
3083
|
|
|
@@ -2365,6 +3172,7 @@ class SynapseOptimizer:
|
|
|
2365
3172
|
induction = 0
|
|
2366
3173
|
ppr = 0
|
|
2367
3174
|
recovery = 0
|
|
3175
|
+
simple_ppr = 0
|
|
2368
3176
|
amp = 0
|
|
2369
3177
|
rise_time = 0
|
|
2370
3178
|
decay_time = 0
|
|
@@ -2388,7 +3196,7 @@ class SynapseOptimizer:
|
|
|
2388
3196
|
if self.run_train_input:
|
|
2389
3197
|
self.tuner._simulate_model(self.train_frequency, self.train_delay)
|
|
2390
3198
|
amp = self.tuner._response_amplitude()
|
|
2391
|
-
ppr, induction, recovery = self.tuner._calc_ppr_induction_recovery(
|
|
3199
|
+
ppr, induction, recovery, simple_ppr = self.tuner._calc_ppr_induction_recovery(
|
|
2392
3200
|
amp, print_math=False
|
|
2393
3201
|
)
|
|
2394
3202
|
amp = self.tuner._find_max_amp(amp)
|
|
@@ -2397,6 +3205,7 @@ class SynapseOptimizer:
|
|
|
2397
3205
|
"induction": float(induction),
|
|
2398
3206
|
"ppr": float(ppr),
|
|
2399
3207
|
"recovery": float(recovery),
|
|
3208
|
+
"simple_ppr": float(simple_ppr),
|
|
2400
3209
|
"max_amplitude": float(amp),
|
|
2401
3210
|
"rise_time": float(rise_time),
|
|
2402
3211
|
"decay_time": float(decay_time),
|
|
@@ -2540,7 +3349,7 @@ class SynapseOptimizer:
|
|
|
2540
3349
|
elif init_guess == "middle_guess":
|
|
2541
3350
|
x0 = [(b[0] + b[1]) / 2 for b in bounds]
|
|
2542
3351
|
else:
|
|
2543
|
-
raise Exception("Pick a
|
|
3352
|
+
raise Exception("Pick a valid init guess method: either 'random' or 'middle_guess'")
|
|
2544
3353
|
normalized_x0 = self._normalize_params(np.array(x0), param_names)
|
|
2545
3354
|
|
|
2546
3355
|
# Run optimization
|
|
@@ -2646,7 +3455,7 @@ class SynapseOptimizer:
|
|
|
2646
3455
|
self.tuner.ispk = None
|
|
2647
3456
|
self.tuner.SingleEvent(plot_and_print=True)
|
|
2648
3457
|
|
|
2649
|
-
# dataclass
|
|
3458
|
+
# dataclass decorator automatically generates __init__ from type-annotated class variables for cleaner code
|
|
2650
3459
|
@dataclass
|
|
2651
3460
|
class GapOptimizationResult:
|
|
2652
3461
|
"""Container for gap junction optimization results"""
|