added lifsuscept figure

This commit is contained in:
Jan Benda 2025-05-16 18:30:02 +02:00
parent 26c0fc4aae
commit b05ca787e1
3 changed files with 123 additions and 52 deletions

Binary file not shown.

View File

@ -1,47 +1,106 @@
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import rc
import mpmath as mp
import matplotlib.pyplot as plt
from pathlib import Path
from plotstyle import plot_style
sims_path = Path('data') / 'simulations'
"""
LIF code from Maria Schlungbaum, Lidner lab, 2024
from threefish.defaults import default_figsize, default_settings
from threefish.load import save_visualization
from threefish.plot.plotstyle import plot_style
from threefish.plot.tags import tag2
from threefish.RAM.calc_analytics import calc_nonlin_analytic_values
from threefish.RAM.reformat_matrix import convert_csv_str_to_float
LIF model in dimensionless units: dv/dt = -v + mu + sqrt(2D)*xi
v: membrane voltage
mu: mean input voltage
D: noise intensity
xi: white Gaussian noise
tau_mem = 1 (membrane time constant, skipped here)
tau_ref: refractory period
vT: threshold voltage
vR: reset voltage
"""
rc('text', usetex=True)
def firingrate(mu, D, tau_ref, vR, vT):
x_start = (mu - vT)/mp.sqrt(2.0*D)
x_end = (mu - vR)/mp.sqrt(2.0*D)
dx = 0.0001
r = 0.0
for i in np.arange(x_start, x_end, dx):
integrand = mp.exp(i**2) * mp.erfc(i)
r += integrand*dx
r0 = 1.0/(tau_ref + mp.sqrt(mp.pi)*r)
return float(r0)
def plot_chi2():
def susceptibility1(omega, r0, mu, D, tau_ref, vR, vT):
delta = (vR**2 - vT**2 + 2.0*mu*(vT - vR))/(4.0*D)
a = (r0 * omega*1.0j)/(mp.sqrt(D) * (omega*1.0j - 1.0))
b = mp.pcfd(omega*1.0j - 1.0, (mu - vT)/mp.sqrt(D)) - mp.exp(delta) * mp.pcfd(omega*1.0j - 1.0, (mu - vR)/mp.sqrt(D))
c = mp.pcfd(omega*1.0j, (mu - vT)/mp.sqrt(D)) - mp.exp(delta) * mp.exp(omega*1.0j*tau_ref) * mp.pcfd(omega*1.0j, (mu - vR)/mp.sqrt(D))
return a * b/c
default_settings() # ts=13, ls=13, fs=13, lw = 0.7
plot_style()
default_figsize(column=2, length=2.5)
fig, ax = plt.subplots(1, 2)
plt.subplots_adjust(bottom=0.2, wspace=0.35, left=0.1, right=0.92)
def susceptibility2(omega1, omega2, chi1_1, chi1_2, r0, mu, D, tau_ref, vR, vT):
delta = (vR**2 - vT**2 + 2.0*mu*(vT - vR))/(4.0*D)
a1 = r0*(1.0 - omega1*1.0j - omega2*1.0j)*(omega1*1.0j + omega2*1.0j)/(2.0*D*(omega1*1.0j - 1.0)*(omega2*1.0j - 1.0))
a2 = (omega1*1.0j + omega2*1.0j)/(2.0*mp.sqrt(D))
a3 = chi1_1/(omega2*1.0j - 1.0) + chi1_2/(omega1*1.0j - 1.0)
b1 = mp.pcfd(omega1*1.0j + omega2*1.0j - 2.0, (mu - vT)/mp.sqrt(D)) - mp.exp(delta) * mp.pcfd(omega1*1.0j + omega2*1.0j - 2.0, (mu - vR)/mp.sqrt(D))
b2 = mp.pcfd(omega1*1.0j + omega2*1.0j - 1.0, (mu - vT)/mp.sqrt(D))
b3 = mp.exp(delta) * mp.pcfd(omega1*1.0j + omega2*1.0j - 1.0, (mu - vR)/mp.sqrt(D))
c = mp.pcfd(omega1*1.0j + omega2*1.0j, (mu - vT)/mp.sqrt(D)) - mp.exp(delta) * mp.exp(1.0j*(omega1 + omega2)*tau_ref) * mp.pcfd(omega1*1.0j + omega2*1.0j, (mu - vR)/mp.sqrt(D))
return a1 * b1/c + a2*a3*b2/c - a2*a3*b3/c
###################################################
transfer_values, suscept_values = calc_nonlin_analytic_values()
###################################################
# plot transfer function, panel A
transfer_values = transfer_values.astype(complex)
ax[0].plot(transfer_values.index, np.abs(transfer_values['transfer']),
color='black')
ax[0].set_xlabel(r'$f$')
ax[0].set_ylabel(r'$|\chi_{1}(f)|$')
ax[0].set_xlim(0, transfer_values.index[-1])
ylim = ax[0].get_ylim()
ax[0].set_ylim(0, ylim[-1])
def susceptibilities(frange, mu, D, tau_ref, vR, vT):
print('compute LIF susceptibilites:')
r0 = firingrate(mu, D, tau_ref, vR, vT)
chi1_data = np.zeros(len(frange), dtype=complex)
chi2_data = np.zeros((len(frange), len(frange)), dtype=complex)
for f2 in range(len(frange)):
print(f' step {f2 + 1:4d} of {len(frange):4d}')
omega2 = 2.0*np.pi*frange[f2]
chi1_2 = susceptibility1(omega2, r0, mu, D, tau_ref, vR, vT)
chi1_data[f2] = chi1_2
for f1 in range(len(frange)):
omega1 = 2.0*np.pi*frange[f1]
chi1_1 = susceptibility1(omega1, r0, mu, D, tau_ref, vR, vT)
chi2 = susceptibility2(omega1, omega2, chi1_1, chi1_2, r0, mu, D, tau_ref, vR, vT)
chi2_data[f2, f1] = chi2
return r0, chi1_data, chi2_data
###################################################
# plot matrix, panel B
suscept_values.columns = suscept_values.index
new_keys, stack_plot = convert_csv_str_to_float(suscept_values)
prss = np.abs(stack_plot)
vmax = np.quantile(prss, 0.994)
def load_lifdata(mu, D, vT=1, vR=0, tau_ref=0):
file_path = sims_path / f'lif-mu{10*mu:03.0f}-D{10000*D:04.0f}-chi2.npz'
if not file_path.exists():
freqs = np.linspace(0.01, 1.0, 200)
r0, chi1, chi2 = susceptibilities(freqs, mu, D, tau_ref, vR, vT)
np.savez(file_path, mu=mu, D=D, vT=vT, vR=vR, tau_mem=1, tau_ref=tau_ref,
r0=r0, freqs=freqs, chi1=chi1, chi2=chi2)
data = np.load(file_path)
r0 = float(data['r0'])
freqs = data['freqs']
chi1 = data['chi1']
chi2 = data['chi2']
return r0, freqs, chi1, chi2
def plot_gain(ax, s, r0, freqs, chi1):
ax.plot(freqs, np.abs(chi1), **s.lsM1)
ax.set_xlabel('$f$')
ax.set_ylabel('$|\\chi_1(f)|$', labelpad=6)
ax.set_xlim(0, 1)
ax.set_ylim(0, 14)
ax.set_xticks_delta(0.2)
ax.set_yticks_delta(3)
def plot_chi2(ax, s, r0, freqs, chi2):
chi2 = np.abs(chi2)
vmax = np.quantile(chi2, 0.996)
ten = 10**np.floor(np.log10(vmax))
for fac, delta in zip([1, 2, 3, 4, 6, 8, 10],
[0.5, 1, 1, 2, 3, 4, 5]):
@ -49,25 +108,37 @@ def plot_chi2():
vmax = fac*ten
ten *= delta
break
pc = ax[1].pcolormesh(new_keys, new_keys, prss, rasterized=True,
cmap='viridis', vmin=0, vmax=vmax)
ax[1].set_aspect('equal')
cax = ax[1].inset_axes([1.04, 0, 0.05, 1])
pc = ax.pcolormesh(freqs, freqs, chi2, vmin=0, vmax=vmax,
rasterized=True)
ax.set_aspect('equal')
ax.set_xlabel('$f_1$')
ax.set_ylabel('$f_2$', labelpad=6)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_xticks_delta(0.2)
ax.set_yticks_delta(0.2)
cax = ax.inset_axes([1.04, 0, 0.05, 1])
cax.set_spines_outward('lrbt', 0)
fig.colorbar(pc, cax=cax, label=r'$|\chi_2(f_1, f_2)|$')
cax.set_yticks_delta(50)
ax[1].set_xlabel(r'$f_{1}$')
ax[1].set_ylabel(r'$f_{2}$')
ax[1].set_xlim(0, 1)
ax[1].set_ylim(0, 1)
ax[1].set_xticks_delta(0.2)
ax[1].set_xticks_delta(0.2)
tag2(axes=ax, xoffs=[-0.5, -5.5], yoffs=1.7) # ax_ams[3],
save_visualization()
cb = fig.colorbar(pc, cax=cax)
cb.outline.set_color('none')
cb.outline.set_linewidth(0)
cax.set_ylabel('$|\\chi_2(f_1, f_2)|$')
cax.set_yticks_delta(ten)
if __name__ == '__main__':
mu = 1.1
D = 0.001
plot_chi2()
r0, freqs, chi1, chi2 = load_lifdata(mu, D)
s = plot_style()
plt.rcParams['axes.labelpad'] = 2
fig, (axg, axc) = plt.subplots(1, 2, cmsize=(s.plot_width, 0.38*s.plot_width))
fig.subplots_adjust(leftm=8, rightm=8.5, topm=1, bottomm=3.5, wspace=0.4)
fig.set_align(autox=False)
plot_gain(axg, s, r0, freqs, chi1)
plot_chi2(axc, s, r0, freqs, chi2)
fig.tag()
fig.savefig()
print()

View File

@ -417,7 +417,7 @@ We here analyze nonlinear responses in two types of primary electroreceptor affe
\section{Introduction}
\begin{figure*}[t]
%\includegraphics[width=\columnwidth]{plot_chi2.pdf}
\includegraphics[width=\columnwidth]{lifsuscept}
\caption{\label{fig:lifresponse} First- (linear) and second-order response functions of the leaky integrate-and-fire model. \figitem{A} Magnitude of the first-order response function $|\chi_1(f)|$, also known as the ``gain'' function, quantifies the response amplitude relative to the stimulus amplitude, both measured at the stimulus frequency. \figitem{B} Magnitude of the second-order response function $|\chi_2(f_1, f_2)|$ quantifies the response at the sum of two stimulus frequencies. For linear systems, the second-order response function is zero, because linear systems do not create new frequencies and thus there is no response at the sum of the two frequencies. The plots show the analytical solutions from \citep{Lindner2001} and \citep{Voronenko2017} with $\mu = 1.1$ and $D = 0.001$. Note that the leaky integrate-and-fire model is formulated without dimensions, frequencies are given in multiples of the inverse membrane time constant.}
\end{figure*}
We like to think about signal encoding in terms of linear relations with unique mapping of a given input value to a certain output of the system under consideration. Indeed, such linear methods, for example the transfer function or first-oder susceptibility shown in figure~\ref{fig:lifresponse}, have been widely and successfully applied to describe and predict neuronal responses and are an invaluable tool to characterize neural systems \citep{Borst1999}. Nonlinear mechanisms, on the other hand, are key on different levels of neural processing. Deciding for one action over another is a nonlinear process on the systemic level. On the cellular level, spiking neurons are inherently nonlinear. Whether an action potential is elicited depends on the membrane potential to exceed a threshold \citep{Hodgkin1952, Koch1995}. Because of such nonlinearities, understanding and predicting neuronal responses to sensory stimuli is in general a difficult task.
@ -495,7 +495,7 @@ Electric fish possess an additional electrosensory system, the passive or ampull
In the example recordings shown above (\figsrefb{fig:punit} and \fref{fig:ampullary}), we only observe nonlinear responses on the anti-diagonal of the second-order susceptibility, where the sum of the two stimulus frequencies matches the neuron's baseline firing rate, which is in line with theoretical expectations \citep{Voronenko2017,Franzen2023}. However, a pronounced nonlinear response at frequencies \foneb{} or \ftwob{}, although predicted by theory, cannot be observed. In the following, we investigate how these discrepancies can be understood.
\begin{figure*}[t]
%\includegraphics[width=\columnwidth]{model_and_data.pdf}
%\includegraphics[width=\columnwidth]{noisesplit}
\notejb{This model in the next figure shows a triangle for 3\% contrast ...}
\notejb{We cannot really make up this twist with the 3\% contrast not converging into a triangle.}
\caption{\label{fig:noisesplit} Estimation of second-order susceptibilities in the limit of weak stimuli. \figitem{A} \suscept{} estimated from $N=11$ 0.5\,s long segments of an electrophysiological recording of another low-CV P-unit (cell 2012-07-03-ak, $\fbase=120$\,Hz, CV=0.20) driven with a weak RAM stimulus with contrast 2.5\,\%. Pink edges mark the baseline firing rate where enhanced nonlinear responses are expected. \figitem[i]{B} \textit{Standard condition} of model simulations with intrinsic noise (bottom) and a RAM stimulus (top). \figitem[ii]{B} \suscept{} estimated from simulations of the cell's LIF model counterpart (cell 2012-07-03-ak, table~\ref{modelparams}) based on the same number of FFT segments $N=11$ as in the electrophysiological recording. \figitem[iii]{B} Same as \panel[ii]{B} but using $10^6$ segments. \figitem[i-iii]{C} Same as in \panel[i-iii]{B} but in the \textit{noise split} condition: there is no external RAM signal driving the model. Instead, a large part (90\,\%) of the total intrinsic noise is treated as a signal and is presented as an equivalent amplitude modulation (\signalnoise, center), while the intrinsic noise is reduced to 10\,\% of its original strength (see methods for details). Simulating one million segments, this reveals the full expected trangular structure of the second-order susceptibility.}