added lifsuscept figure
This commit is contained in:
parent
26c0fc4aae
commit
b05ca787e1
BIN
data/simulations/lif-mu011-D0010-chi2.npz
Normal file
BIN
data/simulations/lif-mu011-D0010-chi2.npz
Normal file
Binary file not shown.
171
lifsuscept.py
171
lifsuscept.py
@ -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()
|
||||
|
@ -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.}
|
||||
|
Loading…
Reference in New Issue
Block a user