HCN1 is a primary HCN Pacemaker Channel in Neurons.
The 3 matches
- [1] § Methods › Simulations of the effects of HCN1 channels in an SCN neuron ↔ HCN_rest-singlePo.py, lines 77–187 · score 0.71 · HCN1 open probability, Brian2 simulation, neuron, leak, trace
- [2] § Methods › Simulations of the effects of HCN1 channels in an SCN neuron ↔ HCN_rest-singlePo.py, lines 26–75 · score 0.71 · potassium reversal potential, steady state, membrane potential, dt, HCN1
- [3] § Methods › Simulations of the effects of HCN1 channels in an SCN neuron ↔ HCN_rest-singlePo.py, lines 77–187 · score 0.57 · voltage ramp, membrane potential, duration, Simulations, HCN, injected
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 · 905 lines · 36 KB · no license · 3 matches
- # Copyright (c) 2026 Christoph Schmidt-Hieber
- # SPDX-License-Identifier: MIT
- # Simulation of HCN channel effects on membrane potential during voltage ramps
- # Code accompanying Enke et al., 2026
- # v2026-01-30
- # Simplified code to compute capacitance from specified membrane time constant (tau_m)
- # v2026-01-21
- # LLM assistance was used to:
- # - document function arguments and return values
- # - assist with plotting
- # - assist with exporting the data to Excel
- # - produce data structures for the Excel export
- # - produce data structures to store the results of the simulations
- # - make the code more readable
- from brian2 import *
- import numpy as np
- import pandas as pd
- import matplotlib.pyplot as plt
- import matplotlib.gridspec as gridspec
- from scipy.interpolate import interp1d
- from itertools import product
- from pathlib import Path
- import pickle
- print("\n".join([f"{name}: {version}" for name, version in [(name, globals()[name].__version__ if hasattr(globals()[name], '__version__') else 'N/A') for name in ['brian2', 'numpy', 'pandas', 'matplotlib', 'scipy', 'itertools', 'pathlib', 'pickle']] if name in globals()]))
- def compute_conductances(V_rest, E_K_param, R_in, scaling, p_rest_rep):
- """
- Compute leak and HCN conductances from the biophysical parameters.
- Args:
- V_rest: Resting membrane potential (quantity).
- E_K_param: Potassium reversal potential (quantity).
- R_in: Input resistance (quantity).
- scaling: Dimensionless scaling factor for HCNX.
- p_rest_rep: Resting open probability (float).
- Returns:
- (g_leak, g_HCN_full, g_HCNX, G_total) as siemens quantities.
- """
- # Remove units and convert to float
- V_r = float(V_rest / volt)
- E_K_val = float(E_K_param / volt)
- E_HCN_val = float(E_HCN / volt)
- G_total = float(1 / R_in / siemens)
- # At the beginning of the ramp we assume that the system is at
- # a steady state and therefore we have two conditions:
- # 1) dV/dt is zero, so the net sum of currents is zero.
- # 2) Membrane potential is at V_rest
- # The total conductance of the cell is given by G_total = 1/R_in
- # and composed of g_leak + g_HCN1_rest + g_HCNX, where:
- # g_HCN1_rest = p_rest_rep * g_HCN_full
- # g_HCNX = scaling * g_HCN_full
- # Therefore:
- # g_leak + (p_rest_rep + scaling) * g_HCN_full = G_total [eq 1]
- # Net sum of currents at steady state is zero, therefore:
- # g_leak * (V_rest - E_K) + g_HCN1_rest * (V_rest - E_HCN) + g_HCNX * (V_rest - E_HCN) = 0
- # We can ignore capacitive currents because dV/dt is zero at steady state, hence C*dV/dt = 0.
- # Rearranging yields:
- # g_leak * (V_rest - E_K) + (p_rest_rep + scaling) * g_HCN_full * (V_rest - E_HCN) = 0 [eq 2]
- # Solving equation 1 for g_leak: g_leak = G_total - (p_rest_rep + scaling) * g_HCN_full,
- # and substituting into equation 2 yields:
- # G_total * (V_rest - E_K) + (p_rest_rep + scaling) * g_HCN_full * (E_K - E_HCN) = 0
- # Rearranging to isolate g_HCN_full gives:
- # G_total * (V_rest - E_K) = (p_rest_rep + scaling) * g_HCN_full * (E_HCN - E_K)
- # Solving for g_HCN_full yields:
- # g_HCN_full = G_total * (V_rest - E_K) / ((p_rest_rep + scaling) * (E_HCN_val - E_K_val))
- denom = (p_rest_rep + scaling) * (E_HCN_val - E_K_val)
- g_HCN_full = G_total * (V_r - E_K_val) / denom
- g_HCNX = scaling * g_HCN_full
- g_HCN1_rest = p_rest_rep * g_HCN_full
- g_leak = G_total - g_HCN1_rest - g_HCNX
- return g_leak * siemens, g_HCN_full * siemens, g_HCNX * siemens, G_total * siemens
- def run_sim(V_rest, E_K_param, R_in, scaling,
- P_HCN1_timed, t_interp, sim_duration, t_start_ramp, dVdt_target,
- p_rest_rep,
- inject_ramp=False, label="sim", rep_idx=None):
- """
- Run a single Brian2 simulation and store summary/trace outputs.
- Args:
- V_rest: Resting membrane potential (quantity).
- E_K_param: Potassium reversal potential (quantity).
- R_in: Input resistance (quantity).
- scaling: Dimensionless scaling factor for HCNX.
- P_HCN1_timed: TimedArray of HCN1 open probability.
- t_interp: Time vector with units for ramp/current construction.
- sim_duration: Total simulation duration (quantity).
- t_start_ramp: Time when the ramp starts (quantity).
- dVdt_target: Target voltage ramp slope (quantity).
- p_rest_rep: Resting open probability (float).
- inject_ramp: Whether to inject the compensatory ramp current.
- label: Key for storing trace outputs.
- rep_idx: Replicate identifier for summary output.
- """
- start_scope()
- g_leak_run, g_HCN_full_run, g_HCNX_run, G_total_run = compute_conductances(
- V_rest, E_K_param, R_in, scaling, p_rest_rep
- )
- # Compute total membrane capacitance from specified membrane time constant:
- # C_total = tau_m / R_in (since tau = R * C -> C = tau / R)
- C_total = tau_m / R_in
- if inject_ramp:
- V_ramp = V_start + dVdt_target * t_interp
- n_hold = int(hold_duration * ms / defaultclock.dt)
- I_hold = np.zeros(n_hold) * amp
- t_ramp = t_interp[n_hold:]
- V_ramp_dyn = V_ramp[n_hold:]
- P_HCN_dyn = P_HCN1_timed(t_ramp)
- I_ramp = -(
- C_total * dVdt_target
- - g_leak_run * (V_ramp_dyn - E_K_param)
- - g_HCN_full_run * P_HCN_dyn * (V_ramp_dyn - E_HCN)
- - g_HCNX_run * (V_ramp_dyn - E_HCN)
- ) # Quantity in amp
- # --- FIX: concatenate unitless magnitudes, then reattach units ---
- I_inj_array = np.concatenate([
- np.asarray(I_hold / amp),
- np.asarray(I_ramp / amp),
- ]) * amp
- else:
- I_inj_array = np.zeros(len(t_interp)) * amp
- I_inj_timed = TimedArray(I_inj_array, dt=defaultclock.dt)
- eqs = '''
- dv/dt = (I_leak + I_HCN1 + I_HCNX + I_inj) / C : volt
- I_leak = -g_leak * (v - E_K) : amp
- I_HCN1 = -g_HCN_full * P_HCN1_timed(t) * (v - E_HCN) : amp
- I_HCNX = -g_HCNX * (v - E_HCN) : amp
- I_inj = I_inj_timed(t) : amp
- C : farad (constant)
- E_K : volt (constant)
- g_leak : siemens (constant)
- g_HCN_full : siemens (constant)
- g_HCNX : siemens (constant)
- '''
- neuron = NeuronGroup(1, eqs, method='euler')
- neuron.v = V_rest
- neuron.C = C_total
- neuron.E_K = E_K_param
- neuron.g_leak = g_leak_run
- neuron.g_HCN_full = g_HCN_full_run
- neuron.g_HCNX = g_HCNX_run
- mon = StateMonitor(neuron, ['v', 'I_HCN1', 'I_leak', 'I_inj'], record=0)
- run(sim_duration)
- time = mon.t / ms
- v = mon.v[0] / mV
- i_hcn1 = mon.I_HCN1[0] / nA
- i_leak = mon.I_leak[0] / nA
- i_inj = mon.I_inj[0] / nA
- p_open = P_HCN1_timed(mon.t)
- i_start = int(t_start_ramp / defaultclock.dt)
- V_max = float(np.max(v[i_start:]))
- summary_data.append({
- "replicate": rep_idx,
- "E_K_mV": float(E_K_param / mV),
- "R_in_Mohm": float(R_in / ohm) / 1e6,
- "scaling": float(scaling),
- "V_max_mV": V_max,
- "injected": bool(inject_ramp),
- "label": label
- })
- time_series_data[label] = {
- "time_ms": np.asarray(time),
- "v_mV": np.asarray(v),
- "i_hcn1_nA": np.asarray(i_hcn1),
- "i_leak_nA": np.asarray(i_leak),
- "i_inj_nA": np.asarray(i_inj),
- "p_open": np.asarray(p_open)
- }
- def closest(val, arr):
- """
- Return the element of arr closest to val.
- Args:
- val: Target value (float-like).
- arr: Iterable of numeric values.
- """
- arr = np.asarray(list(arr), dtype=float)
- return float(arr[np.argmin(np.abs(arr - float(val)))])
- def labels_for_noinj_condition(summary_df, E_K_mV, R_in_Mohm, scaling):
- """
- Get labels matching a non-injected condition in summary_df.
- Args:
- summary_df: DataFrame with summary data and labels.
- E_K_mV: Potassium reversal potential in mV (float).
- R_in_Mohm: Input resistance in MΩ (float).
- scaling: Scaling value (float).
- """
- m = (
- (summary_df["injected"] == False) &
- (summary_df["E_K_mV"] == float(E_K_mV)) &
- (summary_df["R_in_Mohm"] == float(R_in_Mohm)) &
- (summary_df["scaling"] == float(scaling))
- )
- labels = summary_df.loc[m, "label"].dropna().tolist()
- labels = [l for l in labels if l in time_series_data]
- return labels
- def labels_for_injected(summary_df, time_series_data):
- """
- Get labels for injected traces that exist in time_series_data.
- Args:
- summary_df: DataFrame with summary data and labels.
- time_series_data: Dict of trace data keyed by label.
- """
- labels = summary_df.loc[summary_df["injected"] == True, "label"].dropna().tolist()
- labels = [l for l in labels if l in time_series_data]
- if len(labels) > 0:
- return labels
- raise RuntimeError("No injected traces found in summary_df['label'] that exist in time_series_data.")
- def mean_trace(time_series_data, labels, key):
- """
- Compute mean trace by interpolating all traces onto the shortest common time grid
- over the overlapping time window.
- Returns (t_ref, y_mean).
- Args:
- time_series_data: Dict of trace data keyed by label.
- labels: Labels to include in the mean.
- key: Trace field to average (e.g., "v_mV").
- """
- t_list = [np.asarray(time_series_data[l]["time_ms"], dtype=float) for l in labels]
- t_min = max(t[0] for t in t_list)
- t_max = min(t[-1] for t in t_list)
- if not (t_max > t_min):
- raise RuntimeError("No overlapping time window across traces for averaging.")
- masks = [(t >= t_min) & (t <= t_max) for t in t_list]
- lengths = [int(np.sum(m)) for m in masks]
- idx_ref = int(np.argmin(lengths))
- t_ref = t_list[idx_ref][masks[idx_ref]]
- Y = []
- for lbl, t, m in zip(labels, t_list, masks):
- y = np.asarray(time_series_data[lbl][key], dtype=float)
- Y.append(np.interp(t_ref, t[m], y[m]))
- Y = np.vstack(Y)
- return t_ref, np.mean(Y, axis=0)
- def plot_cloud_and_mean(ax, labels, xkey, ykey,
- color="#000000", alpha_cloud=0.25, lw_mean=2.0,
- y_transform=None, label_mean=None):
- """
- Plot all individual traces with the SAME color as the mean (transparent),
- then plot the mean trace on top (opaque).
- Args:
- ax: Matplotlib axis to plot on.
- labels: Labels of traces to plot.
- xkey: X-axis field name (e.g., "time_ms").
- ykey: Y-axis field name (e.g., "v_mV").
- color: Trace/mean color.
- alpha_cloud: Alpha for individual traces.
- lw_mean: Line width for mean trace.
- y_transform: Optional transform applied to y-values.
- label_mean: Legend label for the mean trace.
- """
- # Cloud
- for lbl in labels:
- ts = time_series_data[lbl]
- x = np.asarray(ts[xkey], dtype=float)
- y = np.asarray(ts[ykey], dtype=float)
- if y_transform is not None:
- y = y_transform(y)
- ax.plot(x, y, color=color, alpha=alpha_cloud, linewidth=1.0)
- # Mean
- t_mean, y_mean = mean_trace(time_series_data, labels, ykey)
- if y_transform is not None:
- y_mean = y_transform(y_mean)
- ax.plot(t_mean, y_mean, color=color, alpha=1.0, linewidth=lw_mean,
- label=label_mean)
- def plot_dist(ax, x_vals, df_subset, x_col, xlabel, ylabel=None, semilogx=False,
- point_size=22, point_alpha=0.55, color=None):
- """
- Mean±SD and replicate points. Color optionally set (e.g. to match panel b by E_K).
- Args:
- ax: Matplotlib axis to plot on.
- x_vals: Ordered x-axis values.
- df_subset: DataFrame subset with summary values.
- x_col: Column name for x values in df_subset.
- xlabel: X-axis label.
- ylabel: Y-axis label (or None to skip).
- semilogx: Whether to use a log-scaled x-axis.
- point_size: Marker size for replicate points.
- point_alpha: Alpha for replicate points.
- color: Color for mean±SD and replicate points. If None, use black / gray.
- """
- line_color = color if color is not None else "black"
- point_color = color if color is not None else "0.35"
- g = (df_subset.groupby(x_col, as_index=False)
- .agg(V_mean=("V_max_mV", "mean"),
- V_sd=("V_max_mV", "std"),
- n=("V_max_mV", "count")))
- g = g.set_index(x_col).reindex(x_vals).reset_index()
- x = g[x_col].to_numpy(dtype=float)
- y = g["V_mean"].to_numpy(dtype=float)
- yerr = g["V_sd"].to_numpy(dtype=float)
- if semilogx:
- ax.errorbar(x, y, yerr=yerr, fmt='o-', capsize=3, color=line_color,
- markerfacecolor=line_color, markeredgecolor=line_color, linewidth=1.6)
- ax.set_xscale("log")
- else:
- ax.errorbar(x, y, yerr=yerr, fmt='o-', capsize=3, color=line_color,
- markerfacecolor=line_color, markeredgecolor=line_color, linewidth=1.6)
- # Replicate points: slightly jitter only for semilogx readability; otherwise no jitter is fine here
- reps = df_subset.copy()
- if semilogx:
- jitter = np.exp(np.random.normal(loc=0.0, scale=0.03, size=len(reps)))
- x_rep = reps[x_col].to_numpy(dtype=float) * jitter
- else:
- x_rep = reps[x_col].to_numpy(dtype=float)
- ax.scatter(x_rep, reps["V_max_mV"].to_numpy(dtype=float),
- s=point_size, color=point_color, alpha=point_alpha, edgecolors="none")
- ax.set_xlabel(xlabel)
- if ylabel is not None:
- ax.set_ylabel(ylabel)
- def plot_scaling_curves_by_EK(ax, df, rin_fixed, ek_values, scaling_order,
- ek_color_map,
- xlabel="HCN2-4 scaling", ylabel=None,
- point_size=28, point_alpha=0.65):
- """
- Vmax vs scaling at fixed R_in, with one curve per E_K.
- Replicate points:
- - colored by E_K
- - plotted at exact scaling x positions, with a tiny EK-dependent x-offset
- so different EK groups at the same scaling don't overlap.
- Args:
- ax: Matplotlib axis to plot on.
- df: DataFrame with summary data.
- rin_fixed: Fixed R_in value (MΩ).
- ek_values: Ordered E_K values (mV).
- scaling_order: Ordered scaling values.
- ek_color_map: Mapping from E_K to color.
- xlabel: X-axis label.
- ylabel: Y-axis label (or None to skip).
- point_size: Marker size for replicate points.
- point_alpha: Alpha for replicate points.
- """
- df0 = df[df["R_in_Mohm"] == float(rin_fixed)].copy()
- if df0.empty:
- raise RuntimeError(f"No data for R_in_Mohm == {rin_fixed}")
- # Tiny, fixed offsets per EK (in x-axis units of 'scaling')
- # Chosen to be visually separable but negligible relative to tick spacing.
- offsets = np.linspace(-0.035, 0.035, num=max(1, len(ek_values)))
- ek_to_dx = {float(ek): float(dx) for ek, dx in zip(ek_values, offsets)}
- for ek in ek_values:
- d = df0[df0["E_K_mV"] == float(ek)].copy()
- if d.empty:
- continue
- col = ek_color_map[float(ek)]
- dx = ek_to_dx[float(ek)]
- g = (d.groupby("scaling", as_index=False)
- .agg(V_mean=("V_max_mV", "mean"),
- V_sd=("V_max_mV", "std"),
- n=("V_max_mV", "count")))
- g = g.set_index("scaling").reindex(scaling_order).reset_index()
- x = g["scaling"].to_numpy(float) + dx
- y = g["V_mean"].to_numpy(float)
- yerr = g["V_sd"].to_numpy(float)
- # Mean ± SD (also offset slightly to match the replicate cloud)
- ax.errorbar(x, y, yerr=yerr, fmt='o-', capsize=3, linewidth=1.8,
- color=col, markerfacecolor=col, markeredgecolor=col,
- label=rf"$E_{{\mathrm{{K}}}}={ek:.1f}\,\mathrm{{mV}}$")
- # Replicate points: same scaling within EK, but EK-dependent dx
- ax.scatter(d["scaling"].to_numpy(float) + dx,
- d["V_max_mV"].to_numpy(float),
- s=point_size, color=col, alpha=point_alpha, edgecolors="none")
- ax.set_xlabel(xlabel)
- if ylabel is not None:
- ax.set_ylabel(ylabel)
- # === Constants ===
- # Membrane time constant (tau_m) used to compute total capacitance C_total = tau_m / R_in
- tau_m = 30 * ms
- E_HCN = 0 * mV
- V_start = -81 * mV
- V_end = -53 * mV
- hold_duration = 50 # ms
- sim_dt = 10.0 # µs
- defaultclock.dt = sim_dt * us
- # === Plotting config ===
- # Fixed parameter defaults used in plotting and summaries
- EK_DEFAULT = -95.0 # mV
- RIN_DEFAULT = 100.0 # MΩ
- SC_DEFAULT = 0.0
- # Colors (Matplotlib default cycle colors by explicit hex)
- COL_V = "#1f77b4" # blue
- COL_P = "#ff7f0e" # orange
- COL_HCN = "#2ca02c" # green
- COL_LEAK = "#d62728" # red
- COL_INJ = "#9467bd" # purple
- # For panel b (curves by EK): use 3 distinct colors, consistent for mean+replicates
- EK_COLOR_MAP = None # filled after we know EK values
- # === Simulation cache ===
- USE_SIM_CACHE = True
- SIM_CACHE_PATH = Path("sim_cache.pkl")
- # === Load HCN1 open probability data (time + 11 p-columns) ===
- file_name = "mHCN1_single_20260203.xlsx"
- df = pd.read_excel(file_name)
- iend = 1338
- # time column (shared)
- time_col = pd.to_numeric(df.iloc[:iend, 0], errors="coerce")
- replicates = []
- eps = 0.0 # threshold to define "non-zero"
- N_rest = 10 # first N non-zero points used to estimate p_rest per replicate
- for col_idx in range(1, 12): # columns 1..11
- p_col = pd.to_numeric(df.iloc[:iend, col_idx], errors="coerce")
- valid = (~time_col.isna()) & (~p_col.isna())
- t_vals = time_col[valid].to_numpy(dtype=float)
- p_vals = p_col[valid].to_numpy(dtype=float)
- # Replicate-specific p_rest from first N non-zero points
- n_take = min(N_rest, len(p_vals))
- p_rest_rep = float(np.mean(p_vals[:n_take])) # or np.median(p_vals[:n_take]) for robustness
- # Re-zero time so that dynamic starts at hold_duration cleanly
- t_vals = t_vals - t_vals[0]
- replicates.append((t_vals, p_vals, col_idx-1, p_rest_rep))
- # === Parameter grid derived from explicit value lists ===
- V_rest_values = [-81 * mV]
- E_K_values = [-95 * mV, -92.5 * mV, -90 * mV]
- R_in_values = [1e7 * ohm, 1e8 * ohm, 1e9 * ohm]
- scaling_values = [0.0, 0.5, 1.0, 1.5, 2.0]
- param_grid = [
- {"V_rest": V_rest, "E_K_param": E_K, "R_in": R_in, "scaling": scaling}
- for (V_rest, E_K, R_in, scaling) in product(V_rest_values, E_K_values, R_in_values, scaling_values)
- ]
- # === Run and store simulation results ===
- summary_data = []
- time_series_data = {}
- if USE_SIM_CACHE and SIM_CACHE_PATH.exists():
- with SIM_CACHE_PATH.open("rb") as f:
- cached = pickle.load(f)
- summary_data = cached.get("summary_data", [])
- time_series_data = cached.get("time_series_data", {})
- else:
- # === Run all simulations for each replicate ===
- for t_vals, p_vals, rep_idx, p_rest_rep in replicates:
- # Interpolate HCN open probability for this replicate
- t_hold = np.arange(0, hold_duration, sim_dt / 1000.0)
- p_hold = np.full_like(t_hold, p_rest_rep)
- t_dynamic = t_vals + hold_duration
- p_combined = np.concatenate((p_hold, p_vals))
- t_combined = np.concatenate((t_hold, t_dynamic))
- # float time bases in ms
- t_interp_ms = np.arange(0, t_combined[-1] + sim_dt/1000.0, sim_dt/1000.0)
- p_interp = interp1d(
- t_combined, p_combined, kind="linear", fill_value="extrapolate"
- )(t_interp_ms)
- # TimedArray expects values sampled at defaultclock.dt; dt already set in us
- P_HCN1_timed = TimedArray(p_interp, dt=defaultclock.dt)
- # Keep a quantity time vector for Brian2 computations that expect units
- t_interp = t_interp_ms * ms
- t_start_ramp = hold_duration * ms
- t_end_ramp = t_dynamic[-1] * ms
- ramp_duration = t_end_ramp - t_start_ramp
- dVdt_target = (V_end - V_start) / ramp_duration
- sim_duration = t_end_ramp
- # Baseline runs: exhaustive parameter grid
- for i, params in enumerate(param_grid):
- run_sim(
- **params,
- P_HCN1_timed=P_HCN1_timed,
- t_interp=t_interp,
- sim_duration=sim_duration,
- t_start_ramp=t_start_ramp,
- dVdt_target=dVdt_target,
- p_rest_rep=p_rest_rep,
- inject_ramp=False,
- label=f"rep{rep_idx}_grid{i}",
- rep_idx=rep_idx
- )
- # Injected ramp run
- run_sim(
- V_rest=-81 * mV, E_K_param=EK_DEFAULT * mV, R_in=1e8 * ohm, scaling=SC_DEFAULT,
- P_HCN1_timed=P_HCN1_timed,
- t_interp=t_interp,
- sim_duration=sim_duration,
- t_start_ramp=t_start_ramp,
- dVdt_target=dVdt_target,
- p_rest_rep=p_rest_rep,
- inject_ramp=True,
- label=f"rep{rep_idx}_injected",
- rep_idx=rep_idx
- )
- with SIM_CACHE_PATH.open("wb") as f:
- pickle.dump(
- {"summary_data": summary_data, "time_series_data": time_series_data},
- f,
- protocol=pickle.HIGHEST_PROTOCOL,
- )
- summary_df = pd.DataFrame(summary_data)
- # Normalize legacy column names if needed
- rename_map = {}
- if "E_K" in summary_df.columns and "E_K_mV" not in summary_df.columns:
- rename_map["E_K"] = "E_K_mV"
- if "R_in" in summary_df.columns and "R_in_Mohm" not in summary_df.columns:
- rename_map["R_in"] = "R_in_Mohm"
- if "V_max" in summary_df.columns and "V_max_mV" not in summary_df.columns:
- rename_map["V_max"] = "V_max_mV"
- summary_df = summary_df.rename(columns=rename_map)
- df_no_inj = summary_df[summary_df["injected"] == False].copy()
- df_inj = summary_df[summary_df["injected"] == True].copy()
- # -----------------------------------------------------------------------------
- # Collect labels for traces
- # -----------------------------------------------------------------------------
- # Panels a and d use (E_K, R_in, scaling) = (EK_DEFAULT, RIN_DEFAULT, SC_DEFAULT)
- # (= -95 mV, 100 MΩ, 0 by default).
- labels_noinj = labels_for_noinj_condition(summary_df, EK_DEFAULT, RIN_DEFAULT, SC_DEFAULT)
- if len(labels_noinj) == 0:
- raise RuntimeError(
- "Requested non-injected trace condition not found in summary_data/time_series_data:\n"
- f"E_K={EK_DEFAULT} mV, R_in={RIN_DEFAULT} MΩ, scaling={SC_DEFAULT}"
- )
- labels_inj = labels_for_injected(summary_df, time_series_data)
- # -----------------------------------------------------------------------------
- # Reference values for parameter sweeps (panels b and c)
- # -----------------------------------------------------------------------------
- E_K_vals = sorted(df_no_inj["E_K_mV"].unique())
- R_in_vals = sorted(df_no_inj["R_in_Mohm"].unique())
- sc_vals = sorted(df_no_inj["scaling"].unique())
- E_K_ref_for_rin = closest(EK_DEFAULT, E_K_vals)
- sc_ref_for_rin = closest(SC_DEFAULT, sc_vals)
- df_rin = df_no_inj[(df_no_inj["scaling"] == sc_ref_for_rin) & (df_no_inj["E_K_mV"] == E_K_ref_for_rin)].copy()
- rin_order = sorted(df_rin["R_in_Mohm"].unique())
- ek_values = sorted(df_no_inj["E_K_mV"].unique())
- scaling_order = sorted(df_no_inj["scaling"].unique())
- # Map EK->color (3 curves)
- # Use Matplotlib default qualitative colors for clean separation
- ek_palette = ["#1f77b4", "#ff7f0e", "#2ca02c"] # blue, orange, green
- EK_COLOR_MAP = {float(ek): ek_palette[i % len(ek_palette)] for i, ek in enumerate(ek_values)}
- # -----------------------------------------------------------------------------
- # Figure layout:
- # Top row: a (left) and d (right), aligned, each is a 3-row trace stack
- # Bottom row: b (left, scaling curves by E_K) and c (right, R_in sweep)
- # -----------------------------------------------------------------------------
- fig = plt.figure(figsize=(6, 10)) # wider, less tall (since a and c are side-by-side)
- gs_outer = gridspec.GridSpec(
- 2, 2, figure=fig,
- height_ratios=[3.2, 1.55],
- width_ratios=[1.0, 1.0],
- hspace=0.55, wspace=0.28
- )
- # Subgrids for trace stacks
- gs_a = gridspec.GridSpecFromSubplotSpec(
- 3, 1, subplot_spec=gs_outer[0, 0],
- height_ratios=[1.35, 1.15, 1.05],
- hspace=0.14
- )
- gs_d = gridspec.GridSpecFromSubplotSpec(
- 3, 1, subplot_spec=gs_outer[0, 1],
- height_ratios=[1.35, 1.15, 1.05],
- hspace=0.14
- )
- # Panels b and c in bottom row
- gs_bc = gridspec.GridSpecFromSubplotSpec(
- 1, 2, subplot_spec=gs_outer[1, :],
- width_ratios=[2.0, 1.0],
- wspace=0.35
- )
- # Axes creation
- ax_a1 = fig.add_subplot(gs_a[0, 0])
- ax_a2 = fig.add_subplot(gs_a[1, 0], sharex=ax_a1)
- ax_a3 = fig.add_subplot(gs_a[2, 0], sharex=ax_a1)
- ax_d1 = fig.add_subplot(gs_d[0, 0], sharex=ax_a1, sharey=ax_a1)
- ax_d2 = fig.add_subplot(gs_d[1, 0], sharex=ax_a1, sharey=ax_a2)
- ax_d3 = fig.add_subplot(gs_d[2, 0], sharex=ax_a1, sharey=ax_a3)
- ax_b1 = fig.add_subplot(gs_bc[0, 0])
- ax_c1 = fig.add_subplot(gs_bc[0, 1], sharey=ax_b1)
- fig.subplots_adjust(left=0.08, right=0.98, top=0.96, bottom=0.10)
- # Hide x tick labels on upper axes in stacks
- for ax in (ax_a1, ax_a2, ax_d1, ax_d2):
- ax.tick_params(bottom=False, labelbottom=False)
- # -----------------------------------------------------------------------------
- # Panel a: Non-injected traces (colored clouds + colored means)
- # -----------------------------------------------------------------------------
- plot_cloud_and_mean(ax_a1, labels_noinj, "time_ms", "p_open",
- color=COL_P, alpha_cloud=0.20, lw_mean=2.3)
- ax_a1.set_ylabel(r"$P_{\mathrm{open}}$")
- plot_cloud_and_mean(ax_a2, labels_noinj, "time_ms", "v_mV",
- color=COL_V, alpha_cloud=0.20, lw_mean=2.3)
- ax_a2.set_ylabel(r"$V$ (mV)")
- # Currents: two colors (no patterns)
- plot_cloud_and_mean(ax_a3, labels_noinj, "time_ms", "i_hcn1_nA",
- color=COL_HCN, alpha_cloud=0.18, lw_mean=2.2,
- y_transform=lambda y: -y, label_mean="HCN1 (mean)")
- plot_cloud_and_mean(ax_a3, labels_noinj, "time_ms", "i_leak_nA",
- color=COL_LEAK, alpha_cloud=0.18, lw_mean=2.2,
- y_transform=lambda y: -y, label_mean="Leak (mean)")
- ax_a3.set_ylabel("Current (nA)")
- ax_a3.set_xlabel("Time (ms)")
- ax_a3.legend(frameon=False, fontsize=9, loc="upper right")
- # -----------------------------------------------------------------------------
- # Panel d: Injected traces (colored clouds + colored means)
- # -----------------------------------------------------------------------------
- plot_cloud_and_mean(ax_d1, labels_inj, "time_ms", "p_open",
- color=COL_P, alpha_cloud=0.20, lw_mean=2.3)
- ax_d1.set_ylabel(r"$P_{\mathrm{open}}$")
- plot_cloud_and_mean(ax_d2, labels_inj, "time_ms", "v_mV",
- color=COL_V, alpha_cloud=0.20, lw_mean=2.3)
- ax_d2.set_ylabel(r"$V$ (mV)")
- plot_cloud_and_mean(ax_d3, labels_inj, "time_ms", "i_hcn1_nA",
- color=COL_HCN, alpha_cloud=0.18, lw_mean=2.2,
- y_transform=lambda y: -y, label_mean="HCN1 (mean)")
- plot_cloud_and_mean(ax_d3, labels_inj, "time_ms", "i_leak_nA",
- color=COL_LEAK, alpha_cloud=0.18, lw_mean=2.2,
- y_transform=lambda y: -y, label_mean="Leak (mean)")
- plot_cloud_and_mean(ax_d3, labels_inj, "time_ms", "i_inj_nA",
- color=COL_INJ, alpha_cloud=0.18, lw_mean=2.2,
- y_transform=lambda y: -y, label_mean="Injected (mean)")
- ax_d3.set_ylabel("Current (nA)")
- ax_d3.set_xlabel("Time (ms)")
- ax_d3.legend(frameon=False, fontsize=9, loc="upper right")
- # -----------------------------------------------------------------------------
- # Panel b: Scaling curves by E_K
- # -----------------------------------------------------------------------------
- plot_scaling_curves_by_EK(
- ax_b1,
- df=df_no_inj,
- rin_fixed=RIN_DEFAULT,
- ek_values=ek_values,
- scaling_order=scaling_order,
- ek_color_map=EK_COLOR_MAP,
- xlabel="HCN2-4 scaling",
- ylabel=r"Max $V$ (mV)",
- point_size=34, # larger
- point_alpha=0.60 # less transparent
- )
- # -----------------------------------------------------------------------------
- # Panel c: R_in parameter sweep (same E_K, scaling as panels a/d → match panel b color)
- # -----------------------------------------------------------------------------
- col_c = EK_COLOR_MAP[float(E_K_ref_for_rin)]
- plot_dist(
- ax_c1, rin_order, df_rin, "R_in_Mohm",
- xlabel=r"$R_{\mathrm{in}}$ (M$\Omega$)",
- ylabel=None,
- semilogx=True,
- point_size=34,
- point_alpha=0.65,
- color=col_c
- )
- ax_c1.tick_params(labelleft=False)
- ax_b1.set_title(rf"$R_{{\mathrm{{in}}}} = {RIN_DEFAULT:.0f}\,\mathrm{{M\Omega}}$", fontsize=10, pad=4)
- ax_b1.legend(frameon=False, fontsize=9, loc="best")
- # Remove x-axis line and ticks to emphasize categorical nature of scaling values
- # ax_b1.spines["bottom"].set_visible(False)
- # ax_b1.tick_params(bottom=False, labelbottom=True) # Keep labels, remove ticks
- ax_c1.set_title("mean ± SD (replicates)", fontsize=10, pad=4)
- # -----------------------------------------------------------------------------
- # Styling & panel labels
- # -----------------------------------------------------------------------------
- for ax in (ax_a1, ax_a2, ax_a3, ax_d1, ax_d2, ax_d3, ax_c1, ax_b1):
- ax.spines["top"].set_visible(False)
- ax.spines["right"].set_visible(False)
- ax.grid(False)
- # Panel letters aligned to the top of each block
- bbox_a = ax_a1.get_position()
- bbox_d = ax_d1.get_position()
- bbox_b = ax_b1.get_position()
- bbox_c = ax_c1.get_position()
- fig.text(bbox_a.x0 - 0.06, bbox_a.y1, "a", fontsize=13, fontweight="bold", va="top", ha="left")
- fig.text(bbox_d.x0 - 0.06, bbox_d.y1, "d", fontsize=13, fontweight="bold", va="top", ha="left")
- fig.text(bbox_b.x0 - 0.06, bbox_b.y1, "b", fontsize=13, fontweight="bold", va="top", ha="left")
- fig.text(bbox_c.x0 - 0.06, bbox_c.y1, "c", fontsize=13, fontweight="bold", va="top", ha="left")
- # -----------------------------------------------------------------------------
- # Export data to Excel
- # -----------------------------------------------------------------------------
- excel_file = "figure_data.xlsx"
- with pd.ExcelWriter(excel_file, engine='openpyxl') as writer:
- # Panel a: Non-injected traces
- # Get mean traces
- t_mean_a, v_mean_a = mean_trace(time_series_data, labels_noinj, "v_mV")
- _, p_mean_a = mean_trace(time_series_data, labels_noinj, "p_open")
- _, i_hcn1_mean_a = mean_trace(time_series_data, labels_noinj, "i_hcn1_nA")
- _, i_leak_mean_a = mean_trace(time_series_data, labels_noinj, "i_leak_nA")
- # Create DataFrame with mean traces
- df_panel_a = pd.DataFrame({
- "Time_ms": t_mean_a,
- "Voltage_mean_mV": v_mean_a,
- "P_open_mean": p_mean_a,
- "I_HCN1_mean_nA": -i_hcn1_mean_a, # Apply negative transform as in plot
- "I_leak_mean_nA": -i_leak_mean_a # Apply negative transform as in plot
- })
- # Add individual replicate traces (interpolated to mean time grid)
- for i, label in enumerate(labels_noinj):
- ts = time_series_data[label]
- t_orig = np.asarray(ts["time_ms"], dtype=float)
- df_panel_a[f"Voltage_rep{i+1}_mV"] = np.interp(t_mean_a, t_orig, np.asarray(ts["v_mV"], dtype=float))
- df_panel_a[f"P_open_rep{i+1}"] = np.interp(t_mean_a, t_orig, np.asarray(ts["p_open"], dtype=float))
- df_panel_a[f"I_HCN1_rep{i+1}_nA"] = -np.interp(t_mean_a, t_orig, np.asarray(ts["i_hcn1_nA"], dtype=float))
- df_panel_a[f"I_leak_rep{i+1}_nA"] = -np.interp(t_mean_a, t_orig, np.asarray(ts["i_leak_nA"], dtype=float))
- df_panel_a.to_excel(writer, sheet_name="Panel a", index=False)
- # Panel b: Scaling curves by E_K
- # Find max number of replicates across all E_K and scaling combinations
- df_panel_b_temp = df_no_inj[(df_no_inj["R_in_Mohm"] == float(RIN_DEFAULT))].copy()
- max_reps_b = int(df_panel_b_temp.groupby(["E_K_mV", "scaling"])["V_max_mV"].count().max()) if not df_panel_b_temp.empty else 0
- df_panel_b_list = []
- for ek in ek_values:
- d = df_no_inj[(df_no_inj["E_K_mV"] == float(ek)) &
- (df_no_inj["R_in_Mohm"] == float(RIN_DEFAULT))].copy()
- if d.empty:
- continue
- g_b = (d.groupby("scaling", as_index=False)
- .agg(V_max_mean_mV=("V_max_mV", "mean"),
- V_max_sd_mV=("V_max_mV", "std"),
- n_replicates=("V_max_mV", "count")))
- g_b = g_b.set_index("scaling").reindex(scaling_order).reset_index()
- g_b["E_K_mV"] = ek
- # Create replicate columns
- for rep_idx in range(1, max_reps_b + 1):
- col_name = f"V_max_rep{rep_idx}_mV"
- g_b[col_name] = np.nan
- # Add individual replicate values
- for sc_val in scaling_order:
- reps = d[d["scaling"] == sc_val]["V_max_mV"].values
- for j, rep_val in enumerate(reps):
- col_name = f"V_max_rep{j+1}_mV"
- g_b.loc[g_b["scaling"] == sc_val, col_name] = rep_val
- df_panel_b_list.append(g_b)
- if df_panel_b_list:
- df_panel_b = pd.concat(df_panel_b_list, ignore_index=True)
- # Reorder columns: E_K, scaling, then statistics and replicates
- cols = ["E_K_mV", "scaling", "V_max_mean_mV", "V_max_sd_mV", "n_replicates"]
- rep_cols = [c for c in df_panel_b.columns if c.startswith("V_max_rep")]
- df_panel_b = df_panel_b[cols + rep_cols]
- df_panel_b.to_excel(writer, sheet_name="Panel b", index=False)
- # Panel c: R_in parameter sweep
- g_c = (df_rin.groupby("R_in_Mohm", as_index=False)
- .agg(V_max_mean_mV=("V_max_mV", "mean"),
- V_max_sd_mV=("V_max_mV", "std"),
- n_replicates=("V_max_mV", "count")))
- g_c = g_c.set_index("R_in_Mohm").reindex(rin_order).reset_index()
- # Find max number of replicates to create all columns
- max_reps = int(df_rin.groupby("R_in_Mohm")["V_max_mV"].count().max())
- # Add individual replicate values
- df_panel_c = g_c.copy()
- for rep_idx in range(1, max_reps + 1):
- col_name = f"V_max_rep{rep_idx}_mV"
- df_panel_c[col_name] = np.nan
- for rin_val in rin_order:
- reps = df_rin[df_rin["R_in_Mohm"] == rin_val]["V_max_mV"].values
- for j, rep_val in enumerate(reps):
- col_name = f"V_max_rep{j+1}_mV"
- df_panel_c.loc[df_panel_c["R_in_Mohm"] == rin_val, col_name] = rep_val
- df_panel_c.to_excel(writer, sheet_name="Panel c", index=False)
- # Panel d: Injected traces
- # Get mean traces
- t_mean_d, v_mean_d = mean_trace(time_series_data, labels_inj, "v_mV")
- _, p_mean_d = mean_trace(time_series_data, labels_inj, "p_open")
- _, i_hcn1_mean_d = mean_trace(time_series_data, labels_inj, "i_hcn1_nA")
- _, i_leak_mean_d = mean_trace(time_series_data, labels_inj, "i_leak_nA")
- _, i_inj_mean_d = mean_trace(time_series_data, labels_inj, "i_inj_nA")
- # Create DataFrame with mean traces
- df_panel_d = pd.DataFrame({
- "Time_ms": t_mean_d,
- "Voltage_mean_mV": v_mean_d,
- "P_open_mean": p_mean_d,
- "I_HCN1_mean_nA": -i_hcn1_mean_d, # Apply negative transform as in plot
- "I_leak_mean_nA": -i_leak_mean_d, # Apply negative transform as in plot
- "I_injected_mean_nA": -i_inj_mean_d # Apply negative transform as in plot
- })
- # Add individual replicate traces (interpolated to mean time grid)
- for i, label in enumerate(labels_inj):
- ts = time_series_data[label]
- t_orig = np.asarray(ts["time_ms"], dtype=float)
- df_panel_d[f"Voltage_rep{i+1}_mV"] = np.interp(t_mean_d, t_orig, np.asarray(ts["v_mV"], dtype=float))
- df_panel_d[f"P_open_rep{i+1}"] = np.interp(t_mean_d, t_orig, np.asarray(ts["p_open"], dtype=float))
- df_panel_d[f"I_HCN1_rep{i+1}_nA"] = -np.interp(t_mean_d, t_orig, np.asarray(ts["i_hcn1_nA"], dtype=float))
- df_panel_d[f"I_leak_rep{i+1}_nA"] = -np.interp(t_mean_d, t_orig, np.asarray(ts["i_leak_nA"], dtype=float))
- df_panel_d[f"I_injected_rep{i+1}_nA"] = -np.interp(t_mean_d, t_orig, np.asarray(ts["i_inj_nA"], dtype=float))
- df_panel_d.to_excel(writer, sheet_name="Panel d", index=False)
- print(f"Data exported to {excel_file}")
- # -----------------------------------------------------------------------------
- # Save
- # -----------------------------------------------------------------------------
- fig.savefig("figure_combined.svg", format="svg", bbox_inches="tight")
- fig.savefig("figure_combined.pdf", format="pdf", bbox_inches="tight")
- plt.show()
HCN_rest-singlePo.py, no license · at the source
Overview
- Institut für Physiologie II, Universitätsklinikum Jena, Friedrich-Schiller-Universität Jena, Jena, Germany
- Institut für Physiologie I, Universitätsklinikum Jena, Friedrich-Schiller-Universität Jena, Jena, Germany
Abstract
Rhythmic activity of specialized pacemaker neurons in the brain is necessary to control alertness and circadian timing. Four HCN channels have been identified to generate the pacemaker current Ih or Iq, differing in activation speed, voltage dependence, single-channel conductance, and cAMP sensitivity. Here we show the time-resolved operation of single HCN1, HCN2 and HCN4 channels during the pacemaker depolarization using a dynamic neuronal action potential clamp at femtosiemens resolution. All channels produce a relevant open probability during pacemaker depolarization. However, only mHCN1 channels are significantly activated and deactivated in action potential cycles whereas the gating in mHCN2 and mHCN4 channels is at best barely resolvable and too slow. Simulations suggest that the role of HCN1 channels is to trigger the initial neuronal pacemaker depolarization before other depolarizing conductances take over this role. In conclusion, mHCN1 channels are the primary HCN pacemaker channels that operate as trigger channels for pacemaking.
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 3 matches between paragraphs and lines of code.
OSF 7g5ch
Availability: 1 check, the latest on 29 September 2026: the link answers (HTTP 200)
- 29 September 2026: the link answers (HTTP 200)
3 files
- HCN_rest-singlePo.py, Python, 905 lines, 3 matches
- LICENSE.txt, License, 7 lines
- README.txt, Text, 21 lines
Code availability
The code of the simulations (HCN_rest-singlePo.py) is available at OSF [osf.io/
Reproduced under the paper's license (CC BY), from the paper cited above.
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;
- 1 script, each with its path and the digest of its content;
- 3 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
All data included in the manuscript are deposited in the Open Science Framework [osf.io/
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, 29 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 5 keywords, 10 MeSH terms, 1 funder, 48 references.
Cite
This paper
Enke, U., Schweinitz, A., Tewari, D., Sattler, C., Schmauder, R., Schmidt-Hieber, C., & Benndorf, K. (2026). HCN1 is a primary HCN Pacemaker Channel in Neurons. Nature communications, 17(1), 3745. https://
BibTeX
@article{enke2026hcn1,
author = {Enke, Uta and Schweinitz, Andrea and Tewari, Debanjan and Sattler, Christian and Schmauder, Ralf and Schmidt-Hieber, Christoph and Benndorf, Klaus},
title = {{HCN1 is a primary HCN Pacemaker Channel in Neurons}},
journal = {Nature communications},
year = {2026},
month = apr,
volume = {17},
number = {1},
pages = {3745},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/
url = {https://
pmid = {42026039},
pmcid = {PMC13106851}
}
RIS
TY - JOUR
AU - Enke, Uta
AU - Schweinitz, Andrea
AU - Tewari, Debanjan
AU - Sattler, Christian
AU - Schmauder, Ralf
AU - Schmidt-Hieber, Christoph
AU - Benndorf, Klaus
TI - HCN1 is a primary HCN Pacemaker Channel in Neurons
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/
VL - 17
IS - 1
SP - 3745
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "HCN1 is a primary HCN Pacemaker Channel in Neurons",
"container-title": "Nature communications",
"author": [
{
"family": "Enke",
"given": "Uta"
},
{
"family": "Schweinitz",
"given": "Andrea"
},
{
"family": "Tewari",
"given": "Debanjan"
},
{
"family": "Sattler",
"given": "Christian"
},
{
"family": "Schmauder",
"given": "Ralf"
},
{
"family": "Schmidt-Hieber",
"given": "Christoph"
},
{
"family": "Benndorf",
"given": "Klaus"
}
],
"container-title-short":
"volume": "17",
"issue": "1",
"page": "3745",
"DOI": "10.1038/
"PMID": "42026039",
"PMCID": "PMC13106851",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
23
]
]
}
}
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.1113/ep093939 [code]
- Temperature dependence of HCN channel kinetics: A systematic comparison across mammalian isoforms and species.Journal: Experimental physiologyIn common: intracellular / patch clamp, cellular / molecular, 4 references
- [2] doi:10.1016/j.isci.2026.116930
- HCAR1-mediated lactate signaling modulates motor behavior and regulates spontaneous firing in Purkinje cells.Journal: iScienceIn common: cellular / molecular, 4 references
- [3] doi:10.1523/jneurosci.1506-25.2026 [code]
- Controlling Spatio-Temporal Sequences of Neural Activity by Local Synaptic Changes.Journal: The Journal of neuroscience : the official journal of the Society for NeuroscienceIn common: Brian 2, pandas, SciPy, 2 other tools, cellular / molecular, 1 reference
- [4] doi:10.1002/hipo.70089 [code]
- The Role of Plasticity in Replay: Stability Through Anti-Hebbian Rules.Journal: HippocampusIn common: Brian 2, pandas, SciPy, 2 other tools, cellular / molecular, 1 reference
- [5] doi:10.1371/journal.pcbi.1014752 [code]
- Hierarchical feature binding in a spiking neural network model of the primate ventral visual pathway.Journal: PLoS computational biologyIn common: Brian 2, pandas, SciPy, 2 other tools, 1 reference
- [6] doi:10.1126/sciadv.aee9425 [code]
- Probabilistic inference of homonymous and heteronymous recurrent inhibition in human muscles from large-scale motor neuron recordings.Journal: Science advancesIn common: Brian 2, pandas, SciPy, 2 other tools, 1 reference
- [7] doi:10.1038/s41467-026-75705-2 [code]
- Redundant prefrontal hemispheres adapt storage strategy to working memory demands.Journal: Nature communicationsIn common: Brian 2, pandas, SciPy, 2 other tools, 1 reference
- [8] doi:10.1523/jneurosci.0912-25.2026 [code]
- Hierarchical Afferent Connectivity Drives Population-Wide Bursting Dynamics in a Computational Model of Human-Derived Excitatory Neuronal Networks.Journal: The Journal of neuroscience : the official journal of the Society for NeuroscienceIn common: Brian 2, pandas, SciPy, 2 other tools, 1 reference
- [9] doi:10.1038/s41467-026-75704-3 [code]
- A minimal model of working memory in neural systems and neuromorphic circuits.Journal: Nature communicationsIn common: Brian 2, SciPy, Matplotlib, 1 other tool, 1 reference
- [10] doi:10.1016/j.celrep.2026.117793 [code]
- Clustered inputs engage dendritic nonlinearities and calcium signaling to support efficient place-field formation in CA1 pyramidal neurons.Journal: Cell reportsIn common: Brian 2, pandas, SciPy, 2 other tools, cellular / molecular
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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 1 script, and 3 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:5e8100b0a48019fe…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[.
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.
