OSCR

Reconciling contradictory models of subthalamic nucleus contributions to basal ganglia beta oscillations.

Code ↔ Paper

12 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 12 matches
  1. [1] § Models and methods › Population firing rate model ↔ 01_rate_model/figures_in_paper.ipynb, lines 298–394 · score 0.87 · fast spiking interneurons, subthalamic nucleus, prototypical neurons, population firing rate, receptors, spiny
  2. [2] § Results › Full neuronal network model ↔ 01_rate_model/figures_in_paper.ipynb, lines 298–394 · score 0.85 · fast spiking interneurons, subthalamic nucleus, prototypical neurons, basal ganglia, firing rate models, spiny
  3. [3] § Results › Full neuronal network model › Results on the two-loop spiking network. ↔ 03_spiking_networks/figures_in_paper.ipynb, lines 178–257 · score 0.78 · Error bars, EIF het, QIF het, Proto STN, Beta power, Beta frequency
  4. [4] § Models and methods › Multi-population basal ganglia model › Beta power quantification. ↔ 03_spiking_networks/utils.py, lines 157–187 · score 0.71 · 12.5–30 Hz, Beta power, beta frequency, 12.5 Hz, welch, nperseg
  5. [5] § Results › Full neuronal network model › Results on the two-loop spiking network. ↔ 03_spiking_networks/figures_in_paper.ipynb, lines 121–159 · score 0.69 · EIF het, QIF het, EIF STN, Proto STN, parameter sweeps, network
  6. [6] § Models and methods › Phase difference analysis ↔ 01_rate_model/figures_in_paper.ipynb, lines 396–458 · score 0.64 · dominant frequency, power spectrum, fft, wrapped, firing rate, signals
  7. [7] § Models and methods › Phase difference analysis ↔ 03_spiking_networks/utils.py, lines 283–344 · score 0.63 · dominant frequency, power spectrum, fft, wrapped, firing rate, signals
  8. [8] § Models and methods › Multi-population basal ganglia model › Exponential integrate-and-fire (EIF) model. ↔ 02_tts_and_phase/tts_utils.py, lines 215–241 · score 0.61 · slope factor, exponential term, threshold potential, reset, EIF, fires
  9. [9] § Models and methods › Multi-population basal ganglia model › Power spectral density. ↔ 03_spiking_networks/utils.py, lines 119–155 · score 0.60 · Power spectra, bin width, Welch, segment
  10. [10] § Models and methods › Multi-population basal ganglia model › Average beta frequency. ↔ 03_spiking_networks/utils.py, lines 157–187 · score 0.55 · 12.5–30 Hz, beta frequency, 12.5 Hz, power, population
  11. [11] § Results › Full neuronal network model › Results on the two-loop spiking network. ↔ 03_spiking_networks/sweep_utils.py, lines 184–232 · score 0.55 · multiple seeds, standard error, Beta power, firing rate, network, spiking
  12. [12] § Results › Full neuronal network model › Results on the two-loop spiking network. ↔ 03_spiking_networks/utils.py, lines 415–499 · score 0.52 · Proto firing rates, STN Proto, spiking networks, spectrogram, activation, connection

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 598 lines · 23 KB · no license · 5 matches

  1. import matplotlib.pyplot as plt
  2. import numpy as np
  3. from scipy.signal import welch
  4. from scipy import signal
  5. from network_simulation import NetworkSimulation
  6. import yaml
  7. import os
  8. from scipy.fft import fft, fftfreq
  9. class NetworkVisualization:
  10. def __init__(self, simulation):
  11. """
  12. Initialize the visualization class with a reference to the simulation.
  13. Args:
  14. simulation (EINetworkSimulation): The simulation object containing the data to visualize.
  15. """
  16. self.simulation = simulation
  17. def calculate_firing_rate(self, pop, bin_width=10, normalize=False, drop_initial=0):
  18. """
  19. Calculate the firing rate over time for a population.
  20. Args:
  21. pop (str): The name of the population to plot ('exc' or 'inh').
  22. bin_width (int): The width of each bin in milliseconds.
  23. normalize (bool): Whether to normalize the firing rate.
  24. drop_initial (int): The initial portion of data to drop in milliseconds.
  25. Returns:
  26. tuple: A tuple containing the bin edges and the firing rate.
  27. """
  28. # Get the spike train from the simulation results
  29. spike_train = self.simulation.get_spike_trains()[pop]
  30. # Concatenate all spike times for the population
  31. all_spikes = np.concatenate(spike_train)
  32. if drop_initial > 0:
  33. # Drop the initial portion of data
  34. all_spikes = all_spikes[all_spikes >= drop_initial]
  35. # Define the bins such that each bin edge represents the end of the look-back period
  36. bins = np.arange(drop_initial, self.simulation.T, bin_width)
  37. else:
  38. # Define the bins such that each bin edge represents the end of the look-back period
  39. bins = np.arange(0, self.simulation.T, bin_width)
  40. # Calculate the histogram
  41. spike_counts, _ = np.histogram(all_spikes, bins=bins)
  42. # Calculate the firing rate (spikes per second)
  43. firing_rate = spike_counts / (bin_width / 1000.0 * len(spike_train))
  44. # Normalize if required
  45. if normalize:
  46. firing_rate = firing_rate / firing_rate.max()
  47. return bins[1:], firing_rate
  48. def plot_firing_rate(self, pop, bin_width=10, ax=None, color='blue', normalize=False, drop_initial=0, label=None):
  49. """
  50. Plot the firing rate over time for a population.
  51. Args:
  52. pop (str): The name of the population to plot ('exc' or 'inh').
  53. bin_width (int): The width of each bin in milliseconds.
  54. ax (matplotlib.axes.Axes): The axes to plot on.
  55. color (str): The color of the plot line.
  56. normalize (bool): Whether to normalize the firing rate.
  57. drop_initial (int): The initial portion of data to drop in milliseconds.
  58. """
  59. if ax is None:
  60. fig, ax = plt.subplots()
  61. # Calculate the firing rate
  62. bin_right, firing_rate = self.calculate_firing_rate(pop, bin_width, normalize, drop_initial)
  63. if label is None:
  64. label = f'{pop} Firing Rate'
  65. # Plot the firing rate
  66. ax.plot(bin_right, firing_rate, label=label, color=color)
  67. # Set the title and labels
  68. ax.set_xlabel("Time (ms)")
  69. ax.set_ylabel("Normalized" if normalize else "Firing rate (Hz)")
  70. if ax is None:
  71. plt.show()
  72. def plot_raster(self, pop):
  73. """
  74. Plot a raster plot for the specified population.
  75. Args:
  76. pop (str): The name of the population to plot ('exc' or 'inh').
  77. """
  78. spike_trains = self.simulation.get_results()[f'{pop}_spikes']
  79. # Set up the figure
  80. fig, ax = plt.subplots()
  81. # Loop over each neuron and plot its spike times
  82. for i, spike_train in enumerate(spike_trains):
  83. # i is the neuron index, spike_train is the list of firing times
  84. ax.scatter(spike_train, [i] * len(spike_train), marker='|', color='black')
  85. # Label the axes
  86. ax.set_xlabel('Time (ms)')
  87. ax.set_ylabel('Neuron Index')
  88. ax.set_title(f'Raster Plot of {pop.capitalize()} Neuron Spike Trains')
  89. # Set the y-axis limits to ensure each neuron has its own space
  90. ax.set_ylim(-0.5, len(spike_trains) - 0.5)
  91. # Show the plot
  92. plt.show()
  93. def plot_power_spectrum(self, pop, bin_width=10, nperseg=256, ax=None, normalize=False, drop_initial=0, legend=None):
  94. """
  95. Plot the power spectrum of the firing rate for a given population.
  96. Args:
  97. pop (str): The name of the population to analyze ('exc' or 'inh').
  98. bin_width (int): The width of each bin in milliseconds.
  99. nperseg (int): Length of each segment for the Welch method.
  100. ax (matplotlib.axes.Axes): The axes to plot on.
  101. normalize (bool): Whether to normalize the firing rate.
  102. drop_initial (int): The initial portion of data to drop in milliseconds.
  103. """
  104. if ax is None:
  105. fig, ax = plt.subplots()
  106. # Calculate the firing rate
  107. bin_right, firing_rate = self.calculate_firing_rate(pop, bin_width, normalize, drop_initial)
  108. # Calculate the sampling frequency from bin_right
  109. fs = 1000.0 / (bin_right[1] - bin_right[0]) # Convert bin width from ms to Hz
  110. # Calculate the power spectrum using Welch's method
  111. freqs, power = welch(firing_rate, fs=fs, nperseg=nperseg, nfft=nperseg)
  112. # Plot the power spectrum
  113. if legend is not None:
  114. ax.plot(freqs, power, label=legend)
  115. else:
  116. ax.plot(freqs, power)
  117. ax.set_xlabel('Frequency (Hz)')
  118. ax.set_ylabel('Power')
  119. ax.set_title(f'Power Spectrum of {pop} Firing Rate')
  120. ax.set_xlim(0, 100)
  121. if ax is None:
  122. plt.show()
  123. def calculate_beta_power(self, pop, bin_width=10, nperseg=256, normalize=False, drop_initial=0):
  124. """
  125. Calculate the beta power (average power between 12.5 and 30 Hz) of the firing rate for a given population.
  126. Args:
  127. pop (str): The name of the population to analyze ('exc' or 'inh').
  128. bin_width (int): The width of each bin in milliseconds.
  129. nperseg (int): Length of each segment for the Welch method.
  130. normalize (bool): Whether to normalize the firing rate.
  131. drop_initial (int): The initial portion of data to drop in milliseconds.
  132. Returns:
  133. float: The average power in the beta frequency range (12.5-30 Hz).
  134. """
  135. # Calculate the firing rate
  136. bin_right, firing_rate = self.calculate_firing_rate(pop, bin_width, normalize, drop_initial)
  137. # Calculate the sampling frequency from bin_right
  138. fs = 1000.0 / (bin_right[1] - bin_right[0]) # Convert bin width from ms to Hz
  139. # Calculate the power spectrum using Welch's method
  140. freqs, power = welch(firing_rate, fs=fs, nperseg=nperseg, nfft=nperseg)
  141. # Find the indices of the frequencies in the beta range (12.5-30 Hz)
  142. beta_indices = np.where((freqs >= 12.5) & (freqs <= 30))[0]
  143. # Calculate the average power in the beta range
  144. ave_beta_freq = np.sum(freqs[beta_indices]*power[beta_indices])/np.sum(power[beta_indices])
  145. beta_power = np.mean(power[beta_indices])
  146. return ave_beta_freq, beta_power
  147. def compute_spectrogram(self, pop, bin_width=10, nperseg=256, noverlap=128,
  148. demean=True, drop_initial=0):
  149. """
  150. Compute the spectrogram of the firing rate for a given population.
  151. Args:
  152. pop (str): The name of the population to analyze.
  153. bin_width (int): The width of each bin in milliseconds.
  154. nperseg (int): Length of each segment for the spectrogram.
  155. noverlap (int): Overlap between segments.
  156. demean (bool): Whether to subtract the mean from firing rate.
  157. drop_initial (int): The initial portion of data to drop in milliseconds.
  158. Returns:
  159. tuple: (frequencies, times, Sxx, fs, times_ms)
  160. """
  161. # Calculate the firing rate
  162. bin_right, firing_rate = self.calculate_firing_rate(pop, bin_width, False, drop_initial)
  163. # Z-score the firing rate if requested
  164. if demean:
  165. firing_rate = firing_rate - np.mean(firing_rate)
  166. # Calculate the sampling frequency from bin_right
  167. fs = 1000.0 / (bin_right[1] - bin_right[0]) # Convert bin width from ms to Hz
  168. # Calculate the spectrogram
  169. frequencies, times, Sxx = signal.spectrogram(firing_rate, fs=fs,
  170. nperseg=nperseg,
  171. noverlap=noverlap,
  172. scaling='spectrum')
  173. # Calculate time offset for proper alignment
  174. time_offset = drop_initial + (bin_width / 2) # Add half bin width for center alignment
  175. times_ms = times * 1000 + time_offset # Convert to ms and apply offset
  176. return frequencies, times, Sxx, fs, times_ms
  177. def plot_spectrogram(self, pop, bin_width=10, nperseg=256, noverlap=128, ax=None,
  178. demean=True, drop_initial=0, max_freq=100, cmap='viridis',
  179. global_vmin=None, global_vmax=None, title=None):
  180. """
  181. Plot the spectrogram of the firing rate for a given population.
  182. Args:
  183. pop (str): The name of the population to analyze.
  184. bin_width (int): The width of each bin in milliseconds.
  185. nperseg (int): Length of each segment for the spectrogram.
  186. noverlap (int): Overlap between segments.
  187. ax (matplotlib.axes.Axes): The axes to plot on.
  188. demean (bool): Whether to z-score the firing rate.
  189. drop_initial (int): The initial portion of data to drop in milliseconds.
  190. max_freq (int): Maximum frequency to display in Hz.
  191. cmap (str): Colormap for the spectrogram.
  192. global_vmin (float, optional): Global minimum value for color scaling.
  193. global_vmax (float, optional): Global maximum value for color scaling.
  194. Returns:
  195. matplotlib.image.AxesImage: The spectrogram image
  196. """
  197. if ax is None:
  198. fig, ax = plt.subplots()
  199. # Compute the spectrogram
  200. frequencies, times, Sxx, fs, times_ms = self.compute_spectrogram(
  201. pop, bin_width, nperseg, noverlap, demean, drop_initial
  202. )
  203. # Use global vmin and vmax if provided, otherwise use local min and max
  204. vmin = global_vmin if global_vmin is not None else Sxx.min()
  205. vmax = global_vmax if global_vmax is not None else Sxx.max()
  206. # Plot the spectrogram
  207. im = ax.pcolormesh(times_ms, frequencies, Sxx,
  208. shading='gouraud', cmap=cmap,
  209. vmin=vmin, vmax=vmax)
  210. # Add a colorbar
  211. plt.colorbar(im, ax=ax, label='Power (dB)')
  212. # Set labels and title
  213. ax.set_xlabel('Time (ms)')
  214. ax.set_ylabel('Frequency (Hz)')
  215. if title is not None:
  216. ax.set_title(title)
  217. else:
  218. ax.set_title(f'Spectrogram of {pop} Firing Rate')
  219. ax.set_ylim(0, max_freq) # Set maximum frequency
  220. if ax is None:
  221. plt.show()
  222. return im
  223. def calculate_phase_difference(self, pop1, pop2, bin_width=2, drop_initial=100, min_power_threshold=1e-6):
  224. """
  225. Calculate the phase difference between two populations' firing rates.
  226. Args:
  227. pop1 (str): The name of the first population.
  228. pop2 (str): The name of the second population.
  229. bin_width (int): The width of each bin in milliseconds.
  230. drop_initial (int): The initial portion of data to drop in milliseconds.
  231. Returns:
  232. --------
  233. phase_diff : float
  234. Phase difference in radians (wrapped to [-π, π])
  235. dominant_freq : float
  236. Dominant frequency in Hz
  237. """
  238. # FFT of both signals
  239. _, signal1 = self.calculate_firing_rate(pop1, bin_width=bin_width, drop_initial=drop_initial)
  240. _, signal2 = self.calculate_firing_rate(pop2, bin_width=bin_width, drop_initial=drop_initial)
  241. fft1 = fft(signal1 - signal1.mean())
  242. fft2 = fft(signal2 - signal2.mean())
  243. fs = 1000.0 / bin_width # Sampling frequency in Hz
  244. # Frequency array (positive frequencies only)
  245. freqs = fftfreq(len(signal1), 1 / fs)
  246. pos_freqs = freqs[:len(freqs) // 2]
  247. pos_fft1 = fft1[:len(freqs) // 2]
  248. pos_fft2 = fft2[:len(freqs) // 2]
  249. # Calculate power spectra
  250. power1 = np.abs(pos_fft1) ** 2
  251. power2 = np.abs(pos_fft2) ** 2
  252. # Find dominant frequency from signal1 (our reference)
  253. dominant_idx = np.argmax(np.abs(pos_fft1))
  254. dominant_freq = pos_freqs[dominant_idx]
  255. max_power1 = power1[dominant_idx]
  256. max_power2 = power2[dominant_idx]
  257. # Check if signals have sufficient power at the dominant frequency
  258. if max_power1 < min_power_threshold:
  259. print(f"Warning: Signal 1 ({pop1}) has no significant frequency components (likely constant or noise)")
  260. return np.nan, np.nan
  261. if max_power2 < min_power_threshold:
  262. print("Warning: Signal 2 ({pop2}) has no significant frequency components (likely constant or noise)")
  263. return np.nan, np.nan
  264. # Extract phases at dominant frequency
  265. phase1 = np.angle(pos_fft1[dominant_idx])
  266. phase2 = np.angle(pos_fft2[dominant_idx])
  267. # Calculate phase difference with signal1 as reference
  268. phase_diff = phase2 - phase1
  269. # Wrap to [-π, π]
  270. phase_diff = np.degrees(np.arctan2(np.sin(phase_diff), np.cos(phase_diff)))
  271. return round(phase_diff, 1), round(dominant_freq, 1)
  272. from scipy.optimize import minimize
  273. class StopOptimization(Exception):
  274. pass
  275. def optimize_firing_rates(params_pop, params_conn_all, target_rates,
  276. T=200, dt=0.5, use_gpu=True, drop_time=100, error_threshold=5):
  277. populations = list(target_rates.keys())
  278. last_results = [{'error': float('inf'), 'rates': {pop: 0 for pop in populations}}]
  279. def objective_function(currents):
  280. params_pop_copy = {pop: params_pop[pop].copy() for pop in params_pop}
  281. for i, pop in enumerate(populations):
  282. params_pop_copy[pop]['I'] = currents[i]
  283. sim = NetworkSimulation(params_pop_copy, params_conn_all, T, dt, use_gpu)
  284. sim.simulate(drop_time=drop_time)
  285. error = 0
  286. rates = {}
  287. for i, pop in enumerate(populations):
  288. actual_rate = sim.calculate_average_firing_rate(sim.pop_dict[pop].spike_train, start_time=drop_time)
  289. rates[pop] = actual_rate
  290. if pop == 'D2':
  291. error += (actual_rate - target_rates[pop])**2 * 25
  292. if pop == 'Proto':
  293. error += (actual_rate - target_rates[pop])**2 / 2
  294. else:
  295. error += (actual_rate - target_rates[pop])**2
  296. print(f"{pop}: current={currents[i]:.2f}, rate={actual_rate:.2f} Hz (target: {target_rates[pop]} Hz)")
  297. print(f"Total error: {error:.2f}")
  298. print("-----------------------------------")
  299. last_results[0] = {'error': error, 'rates': rates}
  300. if error < error_threshold:
  301. raise StopOptimization # 💥 Force exit
  302. return error
  303. initial_currents = [params_pop[pop]['I'] for pop in populations]
  304. try:
  305. result = minimize(
  306. objective_function,
  307. initial_currents,
  308. method='Powell',
  309. options={
  310. 'maxiter': 20,
  311. 'disp': True,
  312. 'ftol': 5e-2
  313. }
  314. )
  315. except StopOptimization:
  316. print("Optimization stopped early due to error threshold.")
  317. result = None
  318. optimized_params = {pop: params_pop[pop].copy() for pop in params_pop}
  319. if result and result.success:
  320. for i, pop in enumerate(populations):
  321. optimized_params[pop]['I'] = result.x[i]
  322. else:
  323. # fallback: use last known good currents
  324. for i, pop in enumerate(populations):
  325. optimized_params[pop]['I'] = initial_currents[i]
  326. return optimized_params, result, last_results[0]['rates']
  327. def simulate_stn_intervention(T, params_pop, params_conn_all, dt=0.5, STN_start=400, STN_end=1200, seed = 422, colorbar_range=None):
  328. drop_time = 100
  329. print('STN inactive')
  330. network_sim_1 = NetworkSimulation(params_pop, params_conn_all, T, dt, seed=seed)
  331. activate_population_1 = {'pop_name': 'STN', 'start': 6000, 'end': 7000}
  332. network_sim_1.simulate(drop_time=drop_time, activate_population=activate_population_1)
  333. print('STN active')
  334. network_sim_2 = NetworkSimulation(params_pop, params_conn_all, T, dt, seed=seed)
  335. activate_population_2 = {'pop_name': 'STN', 'start': STN_start, 'end': STN_end}
  336. network_sim_2.simulate(sin_amp=5, drop_time=drop_time, activate_population=activate_population_2)
  337. network_vis_1 = NetworkVisualization(network_sim_1)
  338. network_vis_2 = NetworkVisualization(network_sim_2)
  339. bin_width = 2
  340. # Compute spectrograms first to determine global color scaling
  341. spec1_freq, spec1_times, spec1_Sxx, _, _ = network_vis_1.compute_spectrogram(
  342. pop='Proto',
  343. bin_width=bin_width,
  344. drop_initial=drop_time,
  345. nperseg=64,
  346. noverlap=32
  347. )
  348. spec2_freq, spec2_times, spec2_Sxx, _, _ = network_vis_2.compute_spectrogram(
  349. pop='Proto',
  350. bin_width=bin_width,
  351. drop_initial=drop_time,
  352. nperseg=64,
  353. noverlap=32
  354. )
  355. # # Compute difference of spectrograms (signed)
  356. spec_diff = spec2_Sxx - spec1_Sxx
  357. # print('Difference min',spec_diff.min(), 'max', spec_diff.max())
  358. # Create figure with custom axes
  359. fig = plt.figure(figsize=(20, 10))
  360. ax_spec_diff = fig.add_axes((0.1, 0.7, 1, 0.2))
  361. ax_rate = fig.add_axes((0.1, 0.4, 0.8, 0.2))
  362. ax_stn_proto_rate = fig.add_axes((0.1, 0.1, 0.8, 0.2))
  363. vmin, vmax = (colorbar_range if colorbar_range is not None else (None, None))
  364. im_diff = ax_spec_diff.pcolormesh(
  365. spec2_times * 1000 + drop_time + (bin_width / 2),
  366. spec2_freq,
  367. spec_diff,
  368. shading='gouraud',
  369. cmap='RdBu_r',
  370. vmin=vmin,
  371. vmax=vmax
  372. )
  373. plt.colorbar(im_diff, ax=ax_spec_diff, label='Power Difference (dB)')
  374. ax_spec_diff.set_xlabel('Time (ms)')
  375. ax_spec_diff.set_ylabel('Frequency (Hz)')
  376. ax_spec_diff.set_title('Difference Spectrogram (STN active - STN inactive)')
  377. ax_spec_diff.set_ylim(10, 30)
  378. network_vis_2.plot_firing_rate('Proto', ax=ax_rate, normalize=False, drop_initial=drop_time, color='green',
  379. bin_width=bin_width, label='STN active')
  380. network_vis_1.plot_firing_rate('Proto', ax=ax_rate, normalize=False, drop_initial=drop_time, color='red', bin_width=bin_width, label='STN inactive')
  381. ax_rate.set_xlim(ax_spec_diff.get_xlim())
  382. ax_rate.set_title('Proto Firing Rate')
  383. ax_rate.legend()
  384. ax_rate.axvspan(STN_start, STN_end, color='green', alpha=0.2)
  385. ax_stn_proto_rate_twin = ax_stn_proto_rate.twinx()
  386. network_vis_2.plot_firing_rate('Proto', ax=ax_stn_proto_rate, drop_initial=drop_time, color='green', bin_width=bin_width)
  387. network_vis_2.plot_firing_rate('STN', ax=ax_stn_proto_rate_twin, drop_initial=drop_time, color='blue', bin_width=bin_width)
  388. ax_stn_proto_rate.axvspan(STN_start, STN_end, color='green', alpha=0.2)
  389. ax_stn_proto_rate.set_ylabel('Proto Firing Rate (Hz)')
  390. ax_stn_proto_rate_twin.set_ylabel('STN Firing Rate (Hz)')
  391. ax_stn_proto_rate.set_title('Proto and STN Firing Rate (STN active)')
  392. ax_stn_proto_rate.set_xlim(ax_spec_diff.get_xlim())
  393. plt.show()
  394. phase_diff, ave_freq = network_vis_2.calculate_phase_difference('Proto', 'STN', bin_width=bin_width, drop_initial=STN_start)
  395. print(f"Phase difference between Proto and STN during intervention: {phase_diff} degrees at {ave_freq} Hz")
  396. return network_sim_1, network_sim_2
  397. def calculate_synchronization_index(membrane_potentials):
  398. """
  399. Calculate the synchronization index (chi-squared) from membrane potentials.
  400. Parameters:
  401. -----------
  402. membrane_potentials : numpy.ndarray
  403. Array of shape (n_neurons, n_timepoints) containing membrane potential traces
  404. for each neuron over time.
  405. Returns:
  406. --------
  407. chi_squared : float
  408. The synchronization index, bounded to [0, 1] interval.
  409. """
  410. # Number of neurons
  411. N = membrane_potentials.shape[0]
  412. # Calculate the LFP-like signal (average membrane potential across neurons)
  413. lfp = np.mean(membrane_potentials, axis=0)
  414. # Calculate variance of the LFP signal
  415. var_lfp = np.var(lfp)
  416. # Calculate variance of each neuron's membrane potential
  417. var_individual = np.var(membrane_potentials, axis=1)
  418. # Calculate the sum of individual variances
  419. sum_individual_var = np.sum(var_individual)
  420. # Calculate chi-squared
  421. chi_squared = (N * var_lfp) / sum_individual_var
  422. return chi_squared
  423. def load_params_yaml(file_path='params1.yaml', dt=0.5):
  424. """
  425. Load parameters from a YAML file.
  426. Args:
  427. file_path (str): Path to the YAML file
  428. Returns:
  429. dict: Loaded parameters with dt placeholders replaced
  430. """
  431. # Check if file exists
  432. if not os.path.exists(file_path):
  433. raise FileNotFoundError(f"Parameter file not found: {file_path}")
  434. # Load the YAML file
  435. with open(file_path, 'r') as file:
  436. # Load YAML content
  437. params = yaml.safe_load(file)
  438. # Replace ${dt} placeholders with actual dt value
  439. params_str = yaml.dump(params)
  440. params_str = params_str.replace('${dt}', str(dt))
  441. params = yaml.safe_load(params_str)
  442. params_pop = params.get('params_pop', {})
  443. params_conn_all = params.get('params_conn_all', [])
  444. return params_pop, params_conn_all
  445. def get_selected_params(params_pop, params_conn_all, selected_pops):
  446. """
  447. Get selected population parameters and their connections.
  448. Args:
  449. params_pop (dict): Dictionary of population parameters
  450. params_conn_all (list): List of connection dictionaries
  451. selected_pops (list): List of population names to include.
  452. If None, returns Proto, FSI/FSN, and D2.
  453. Returns:
  454. tuple: (selected_pop_params, selected_connections)
  455. """
  456. # Create a deep copy to avoid modifying the original
  457. selected_pop_params = {}
  458. for pop in selected_pops:
  459. if 'STN' in pop:
  460. selected_pop_params['STN'] = params_pop[pop]
  461. elif pop in params_pop:
  462. selected_pop_params[pop] = params_pop[pop]
  463. else:
  464. raise ValueError(f"Population '{pop}' not found in params_pop.")
  465. # Only include connections between selected populations
  466. selected_connections = []
  467. for conn in params_conn_all:
  468. source = conn['source']
  469. target = conn['target']
  470. # Include connection if both source and target are in selected populations
  471. if source in selected_pop_params and target in selected_pop_params:
  472. selected_connections.append(conn.copy())
  473. return selected_pop_params, selected_connections

utils.py at commit d2babf6, no license · at the source

Overview

Authors: Ka Nap Tse1, G. Bard Ermentrout1, Jonathan E. Rubin1
  1. Department of Mathematics, University of Pittsburgh, Pittsburgh, Pennsylvania, United States of America
Institutions: University of Pittsburgh (United States)
Journal: PLoS computational biology, volume 22, issue 8, article e1013942
Dates: received 23 January 2026; accepted 2 July 2026; published online 17 August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1371/journal.pcbi.1013942 · PMID 42607099 · PMCID PMC13541254 · OpenAlex W7125773214
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism), Parkinson's (population), computational (subfield)
Methods: Spectral & time-frequency, Connectivity, Single-unit activity, calcium imaging
MeSH: Basal Ganglia*, Beta Rhythm*, Models, Neurological*, Subthalamic Nucleus*, Action Potentials, Animals, Computational Biology, Computer Simulation, Humans, Nerve Net, Neurons, Parkinson Disease (* major topic)
Journal subjects: Biology and Life Sciences, Cell Biology, Cellular Types, Animal Cells, Neurons, Neuroscience, Cellular Neuroscience, Computational Biology, Computational Neuroscience, Single Neuron Function, Computer and Information Sciences, Neural Networks, Anatomy, Brain, Basal Ganglia, Medicine and Health Sciences, Physiology, Electrophysiology, Membrane Potential, Action Potentials, Neurophysiology, Population Biology, Population Dynamics, Research and Analysis Methods, Crystallographic Techniques, Phase Determination
Topic: Neurological disorders and treatments (Neurology, Medicine), according to OpenAlex
Funding: NIH (R01NS125814, R01DA059993)
Citations: not cited yet (Europe PMC); 50 references in the paper

Abstract

Recent computational studies of Parkinson’s disease have yielded contradictory findings regarding the role of the subthalamic nucleus (STN) in pathological beta oscillations, with some models implicating STN as essential for beta generation and others suggesting that STN suppresses oscillations. This work addresses these discrepancies by systematically investigating how the specific features of the integrate-and-fire neurons used in these models influence simulated basal ganglia network dynamics. Using both rate models and spiking network simulations incorporating coupled subthalamopallidal and pallidostriatal circuits, we demonstrate that the choice between leaky integrate-and-fire (LIF) and quadratic integrate-and-fire (QIF) models to represent STN neurons fundamentally impacts the phase relationship between STN and external globus pallidus prototypical (Proto) neuron populations. QIF STN neurons establish in-phase coupling with Proto neurons, which enhances beta oscillation amplitude, while LIF STN neurons develop anti-phase relationships that suppress beta power. Through intervention experiments and parameter sweeps across physiologically relevant firing rates, we show that these phase-related effects persist robustly across network conditions, and we mathematically establish conditions under which these results are guaranteed to hold. Our findings reveal that the fundamental mathematical structure underlying spike generation, rather than other biophysical details, determines whether the subthalamopallidal loop acts as a beta amplifier or suppressor. This mechanistic insight reconciles contradictory findings in the literature, demonstrates that seemingly minor modeling choices can have profound consequences for understanding disease mechanisms and therapeutic targets, and offers predictions for determining which model framework reflects the biological reality.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 12 matches between paragraphs and lines of code.

kanaptse/BG-Oscillation-Synergy-Suppression

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: d2babf669d92ecac936c92ae194347df5021460a, 24 May 2026
Languages: Python (9), Jupyter (8)
Size: 109 files, 17 scripts
Software Heritage: not archived
Found in: “Data Availability”
Holds: README, environment (requirements.txt), 6 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (17 files), Matplotlib (13 files), SciPy (4 files), CuPy (3 files), pandas (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
18 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 17 scripts, each with its path and the digest of its content;
  • 12 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data Availability

The codes underlying our findings are openly available at: https://github.com/kanaptse/BG-Oscillation-Synergy-Suppression We did not generate any data for this study.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 12 MeSH terms, 1 funder, 50 references.

Cite

This paper

Tse, K. N., Ermentrout, G. B., & Rubin, J. E. (2026). Reconciling contradictory models of subthalamic nucleus contributions to basal ganglia beta oscillations. PLoS computational biology, 22(8), e1013942. https://doi.org/10.1371/journal.pcbi.1013942

BibTeX

@article{tse2026reconciling,
author = {Tse, Ka Nap and Ermentrout, G. Bard and Rubin, Jonathan E.},
title = {{Reconciling contradictory models of subthalamic nucleus contributions to basal ganglia beta oscillations}},
journal = {PLoS computational biology},
year = {2026},
month = aug,
volume = {22},
number = {8},
pages = {e1013942},
publisher = {PLOS},
issn = {1553-734X},
doi = {10.1371/journal.pcbi.1013942},
url = {https://doi.org/10.1371/journal.pcbi.1013942},
pmid = {42607099},
pmcid = {PMC13541254}
}

RIS

TY - JOUR
AU - Tse, Ka Nap
AU - Ermentrout, G. Bard
AU - Rubin, Jonathan E.
TI - Reconciling contradictory models of subthalamic nucleus contributions to basal ganglia beta oscillations
T2 - PLoS computational biology
J2 - PLoS Comput Biol
PY - 2026
DA - 2026/08/17
VL - 22
IS - 8
SP - e1013942
SN - 1553-734X
PB - PLOS
DO - 10.1371/journal.pcbi.1013942
UR - https://doi.org/10.1371/journal.pcbi.1013942
LA - en
ER -

CSL-JSON

{
"id": "10.1371/journal.pcbi.1013942",
"type": "article-journal",
"title": "Reconciling contradictory models of subthalamic nucleus contributions to basal ganglia beta oscillations",
"container-title": "PLoS computational biology",
"author": [
{
"family": "Tse",
"given": "Ka Nap"
},
{
"family": "Ermentrout",
"given": "G. Bard"
},
{
"family": "Rubin",
"given": "Jonathan E."
}
],
"container-title-short": "PLoS Comput Biol",
"volume": "22",
"issue": "8",
"page": "e1013942",
"DOI": "10.1371/journal.pcbi.1013942",
"PMID": "42607099",
"PMCID": "PMC13541254",
"ISSN": "1553-734X",
"publisher": "PLOS",
"URL": "https://doi.org/10.1371/journal.pcbi.1013942",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
17
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1523/eneuro.0283-26.2026 [code]
A Cortico-Basal Ganglia-Thalamic Network Model Linking Intermittent Postural Control to Sway-Related Beta-Band Oscillations.
Journal: eNeuro
In common: SciPy, Matplotlib, NumPy, computational, Parkinson's, EEG, 6 references
[2] doi:10.1016/j.ebiom.2026.106418 [code]
Corticostriatal glutamate mechanisms underlying beta synchrony and motor deficits via striatal NMDA receptors in Parkinson's disease.
Journal: EBioMedicine
In common: pandas, SciPy, Matplotlib, 1 other tool, Parkinson's, EEG, 4 references
[3] doi:10.1038/s41467-026-71426-8 [code]
Distinct modes of dopamine modulation on striatopallidal synaptic transmission.
Journal: Nature communications
In common: pandas, SciPy, Matplotlib, 1 other tool, 4 references
[4] doi:10.1016/j.xcrm.2026.103001
Cortical-to-pallidal beta cascade underlies network pathophysiology in Parkinson's disease.
Journal: Cell reports. Medicine
In common: Parkinson's, 5 references
[5] doi:10.1038/s41531-026-01372-1 [code]
Varying patterns of association between cortical large-scale networks and subthalamic nucleus activity in Parkinson's disease.
Journal: NPJ Parkinson's disease
In common: pandas, SciPy, Matplotlib, 1 other tool, Parkinson's, 2 references
[6] doi:10.1371/journal.pcbi.1013382 [code]
On the role of L-type Ca2+ and BK channels in a biophysical model of cartwheel interneurons.
Journal: PLoS computational biology
In common: computational, author Jonathan E. Rubin
[7] doi:10.3390/s26134019 [code]
NeuroStat: An Open-Source EEG Connectivity Platform for Randomised Controlled Trials.
Journal: Sensors (Basel, Switzerland)
In common: CuPy, pandas, SciPy, 2 other tools, EEG
[8] doi:10.1371/journal.pone.0347671 [code]
RMETNet: A cross-subject motor imagery EEG signal classification model based on TSLANet and riemannian geometry features.
Journal: PloS one
In common: CuPy, pandas, SciPy, 2 other tools, EEG
[9] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: CuPy, pandas, SciPy, 2 other tools
[10] doi:10.1371/journal.pcbi.1014555 [code]
Body surface potential driven personalisation of electrophysiological digital twins in hypertrophic cardiomyopathy.
Journal: PLoS computational biology
In common: CuPy, pandas, SciPy, 2 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.