codexpast-exam-of-the-mathematics-and-the-environment-and-mathematical-physiology-course-of-the-university-of-oxford2007-calcium-pulse-phase-plane.py
past-exam-of-the-mathematics-and-the-environment-and-mathematical-physiology-course-of-the-university-of-oxford/2007-calcium-pulse-phase-plane.py
"""Qualitative matching diagram, not a computed solution of specified kinetics."""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
def g(u):
return (u-2)**3-3*(u-2)+5
def bezier(points):
p = np.asarray(points);t = np.linspace(0, 1, 120)[:, None]
return (1-t)**3*p[0]+3*(1-t)**2*t*p[1]+3*(1-t)*t*t*p[2]+t**3*p[3]
def arrow(ax, xy, color):
j=len(xy)//2
ax.annotate('', xy=xy[j+5], xytext=xy[j-5], arrowprops=dict(arrowstyle='->', color=color, lw=1.7))
def main():
u=np.linspace(.18, 3.6, 500)
ub=.5;uc=2+(1.5+np.sqrt(1.25))/2;ud=3.12
roots=np.roots([1,0,-2,7-(ud+g(ud))]);ua=2+min(roots.real)
b=np.array([ub,g(ub)]);c=np.array([uc,g(uc)]);d=np.array([ud,g(ud)]);a=np.array([ua,g(ua)])
fig, ax=plt.subplots(figsize=(7.0, 4.8), layout='constrained')
ax.plot(u,g(u),color='#777',lw=1.6,label='Nullcline V = g(U)')
peak=np.array([1.3,g(1.3)]);trough=np.array([2.9,g(2.9)])
paths=[np.vstack([bezier([b,[.72,6.7],[1.08,g(1.3)],peak]),bezier([peak,[1.85,g(1.3)],[3.1,4.1],c])]),np.vstack([bezier([d,[3.05,3.031],[2.98,g(2.9)],trough]),bezier([trough,[2.2,g(2.9)],[.7,4.9],a])])]
for j,xy in enumerate(paths):
ax.plot(xy[:,0],xy[:,1],'--',color='#a34722',lw=1.8,label='Required fast connections (schematic)'if j==0 else None)
arrow(ax,xy,'#a34722')
for j,(left,right)in enumerate([(uc,ud),(ua,ub)]):
vals=np.linspace(left,right,80);xy=np.c_[vals,g(vals)]
ax.plot(xy[:,0],xy[:,1],color='#087a70',lw=3,label='Slow portions on outer branches'if j==0 else None)
arrow(ax,xy,'#087a70')
offsets={'A':(-19,-13),'B':(-16,9),'C':(9,9),'D':(10,-15)}
for name,point in zip('ABCD',[a,b,c,d]):
ax.scatter(*point,color='#222',s=22,zorder=5);ax.annotate(name,point,xytext=offsets[name],textcoords='offset points',fontsize=11)
ax.set(xlim=(.05,3.8),ylim=(1.7,8.1),xlabel='U',ylabel='V',title='Spike matching: B → C → D → A → B')
ax.text(.04,.04,'Dashed paths indicate connections to be found by shooting.\nNo numerical speed or kinetic function is specified.',transform=ax.transAxes,fontsize=8.5,bbox=dict(facecolor='white',edgecolor='none',alpha=.9))
ax.grid(alpha=.15);ax.legend(loc='upper right',fontsize=8)
fig.savefig(Path.cwd()/(Path(__file__).stem+'.png'),dpi=120,facecolor='white');plt.close(fig)
if __name__=='__main__':
main()