├── .gitignore ├── LICENSE ├── README.md ├── dev ├── concept1.py ├── concept2.py ├── concept3.png ├── concept3.py ├── concept4.png ├── concept4.py └── figures │ ├── 320px-Hodgkin-Huxley.svg.png │ └── 800px-Hodgkin-Huxley.svg.png ├── src └── pyhh │ ├── __init__.py │ ├── models.py │ └── simulations.py └── tests ├── demo.png └── demo.py /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Scott W Harden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyHH 2 | **pyHH is a simple Python implementation of the Hodgkin-Huxley spiking neuron model.** pyHH simulates conductances and calculates membrane voltage at discrete time points without requiring a differential equation solver. [HHSharp](https://github.com/swharden/HHSharp) is a similar project written in C#. 3 | 4 | ![](dev/concept4.png) 5 | 6 | ### Minimal Code Example 7 | A full Hodgkin-Huxley spiking neuron model and simulation was created in fewer than 100 lines of Python ([dev/concept4.py](dev/concept4.py)). Unlike other code examples on the internet, this implementation is object-oriented and Pythonic. When run, it produces the image above. 8 | 9 | ## Python Package 10 | The `pyhh` package includes Hodgkin-Huxley models and additional tools to organize simulation data. 11 | 12 | ### Simulation Steps 13 | 14 | 1. Create a model cell and customize its properties if desired 15 | 2. Create a stimulus waveform (a numpy array) 16 | 3. Create a simulation by giving it model the waveform you created 17 | 4. Plot various properties of the stimulation 18 | 19 | ### Example Usage 20 | 21 | ```python 22 | # customize a neuron model if desired 23 | model = pyhh.HHModel() 24 | model.gNa = 100 # typically 120 25 | model.gK = 5 # typically 36 26 | model.EK = -35 # typically -12 27 | 28 | # customize a stimulus waveform 29 | stim = np.zeros(20000) 30 | stim[7000:13000] = 50 # add a square pulse 31 | 32 | # simulate the model cell using the custom waveform 33 | sim = pyhh.Simulation(model) 34 | sim.Run(stimulusWaveform=stim, stepSizeMs=0.01) 35 | ``` 36 | 37 | ```python 38 | # plot the results with MatPlotLib 39 | plt.figure(figsize=(10, 8)) 40 | 41 | ax1 = plt.subplot(411) 42 | ax1.plot(sim.times, sim.Vm - 70, color='b') 43 | ax1.set_ylabel("Potential (mV)") 44 | ax1.set_title("Hodgkin-Huxley Spiking Neuron Model", fontSize=16) 45 | 46 | ax2 = plt.subplot(412) 47 | ax2.plot(sim.times, stim, color='r') 48 | ax2.set_ylabel("Stimulation (µA/cm²)") 49 | 50 | ax3 = plt.subplot(413, sharex=ax1) 51 | ax3.plot(sim.times, sim.StateH, label='h') 52 | ax3.plot(sim.times, sim.StateM, label='m') 53 | ax3.plot(sim.times, sim.StateN, label='n') 54 | ax3.set_ylabel("Activation (frac)") 55 | ax3.legend() 56 | 57 | ax4 = plt.subplot(414, sharex=ax1) 58 | ax4.plot(sim.times, sim.INa, label='VGSC') 59 | ax4.plot(sim.times, sim.IK, label='VGKC') 60 | ax4.plot(sim.times, sim.IKleak, label='KLeak') 61 | ax4.set_ylabel("Current (µA/cm²)") 62 | ax4.set_xlabel("Simulation Time (milliseconds)") 63 | ax4.legend() 64 | 65 | plt.tight_layout() 66 | plt.savefig("tests/demo.png") 67 | plt.show() 68 | ``` 69 | 70 | ![](tests/demo.png) 71 | 72 | ## Theory 73 | 74 | Visit https://github.com/swharden/HHSharp for code concepts and simulation notes. Although the language is different, the biology and implementation is the same. 75 | 76 | ![](https://raw.githubusercontent.com/swharden/HHSharp/master/dev/theory.png) 77 | 78 | ### Additional Resources 79 | * [Hodgkin and Huxley, 1952](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1392413/pdf/jphysiol01442-0106.pdf) (the original manuscript) 80 | * [The Hodgkin-Huxley Mode](http://www.genesis-sim.org/GENESIS/iBoG/iBoGpdf/chapt4.pdf) (The GENESIS Simulator, Chapter 4) 81 | * Wikipedia: [Hodgkin–Huxley model](https://en.wikipedia.org/wiki/Hodgkin%E2%80%93Huxley_model) 82 | * [Hodgkin-Huxley spiking neuron model in Python](https://www.bonaccorso.eu/2017/08/19/hodgkin-huxley-spiking-neuron-model-python/) by Giuseppe Bonaccorso - a HH model which uses the [`scipy.integrate.odeint` ordinary differential equation solver](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html) 83 | * [Introduction to Computational Modeling: Hodgkin-Huxley Model](http://andysbrainblog.blogspot.com/2013/10/introduction-to-computational-modeling.html) by Andrew Jahn - a commentary of the HH model with matlab code which discretely simulates conductances 84 | * [NeuroML Hodgkin Huxley Tutorials](https://github.com/swharden/hodgkin_huxley_tutorial) 85 | * [Summary of the Hodgkin-Huxley model](http://ecee.colorado.edu/~ecen4831/HHsumWWW/HHsum.html) by Dave Beeman 86 | -------------------------------------------------------------------------------- /dev/concept1.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | # adapted from https://www.bonaccorso.eu/2017/08/19/hodgkin-huxley-spiking-neuron-model-python/ 5 | 6 | # simulation time (millisecond units) 7 | tmin = 0.0 8 | tmax = 50.0 9 | T = np.linspace(tmin, tmax, 10000) 10 | 11 | # channel conductances (mS/cm^2) 12 | gK = 36.0 13 | gNa = 120.0 14 | gL = 0.3 # leak 15 | 16 | # ion reversal potentials (mV) 17 | VK = -12.0 18 | VNa = 115.0 19 | Vl = 10.613 # Leak 20 | 21 | # membrane properties 22 | Cm = 1.0 # capacitance (uF/cm^2) 23 | 24 | # Potassium ion-channel rate functions 25 | 26 | 27 | def alpha_n(Vm): 28 | return (0.01 * (10.0 - Vm)) / (np.exp(1.0 - (0.1 * Vm)) - 1.0) 29 | 30 | 31 | def beta_n(Vm): 32 | return 0.125 * np.exp(-Vm / 80.0) 33 | 34 | # Sodium ion-channel rate functions 35 | 36 | 37 | def alpha_m(Vm): 38 | return (0.1 * (25.0 - Vm)) / (np.exp(2.5 - (0.1 * Vm)) - 1.0) 39 | 40 | 41 | def beta_m(Vm): 42 | return 4.0 * np.exp(-Vm / 18.0) 43 | 44 | 45 | def alpha_h(Vm): 46 | return 0.07 * np.exp(-Vm / 20.0) 47 | 48 | 49 | def beta_h(Vm): 50 | return 1.0 / (np.exp(3.0 - (0.1 * Vm)) + 1.0) 51 | 52 | # n, m, and h steady-state values 53 | 54 | 55 | def n_inf(Vm=0.0): 56 | return alpha_n(Vm) / (alpha_n(Vm) + beta_n(Vm)) 57 | 58 | 59 | def m_inf(Vm=0.0): 60 | return alpha_m(Vm) / (alpha_m(Vm) + beta_m(Vm)) 61 | 62 | 63 | def h_inf(Vm=0.0): 64 | return alpha_h(Vm) / (alpha_h(Vm) + beta_h(Vm)) 65 | 66 | # Input stimulus 67 | 68 | 69 | def Id(t): 70 | if 0.0 < t < 1.0: 71 | return 150.0 72 | elif 10.0 < t < 11.0: 73 | return 50.0 74 | return 0.0 75 | 76 | # Compute derivatives 77 | 78 | 79 | def compute_derivatives(y, t0): 80 | dy = np.zeros((4,)) 81 | 82 | Vm = y[0] 83 | n = y[1] 84 | m = y[2] 85 | h = y[3] 86 | 87 | # dVm/dt 88 | GK = (gK / Cm) * np.power(n, 4.0) 89 | GNa = (gNa / Cm) * np.power(m, 3.0) * h 90 | GL = gL / Cm 91 | 92 | dy[0] = (Id(t0) / Cm) - (GK * (Vm - VK)) - \ 93 | (GNa * (Vm - VNa)) - (GL * (Vm - Vl)) 94 | 95 | # dn/dt 96 | dy[1] = (alpha_n(Vm) * (1.0 - n)) - (beta_n(Vm) * n) 97 | 98 | # dm/dt 99 | dy[2] = (alpha_m(Vm) * (1.0 - m)) - (beta_m(Vm) * m) 100 | 101 | # dh/dt 102 | dy[3] = (alpha_h(Vm) * (1.0 - h)) - (beta_h(Vm) * h) 103 | 104 | return dy 105 | 106 | 107 | # State (Vm, n, m, h) 108 | Y = np.array([0.0, n_inf(), m_inf(), h_inf()]) 109 | 110 | # Solve ODE system 111 | from scipy.integrate import odeint 112 | Vy = odeint(compute_derivatives, Y, T) 113 | 114 | voltage = Vy[:, 0] 115 | dndt = Vy[:, 1] 116 | dmdt = Vy[:, 2] 117 | dhdt = Vy[:, 3] 118 | 119 | plt.figure() 120 | 121 | ax1 = plt.subplot(211) 122 | ax1.plot(voltage) 123 | 124 | ax2 = plt.subplot(212, sharex = ax1) 125 | ax2.plot(dndt, label="dn/dt") 126 | ax2.plot(dmdt, label="dm/dt") 127 | ax2.plot(dhdt, label="dh/dt") 128 | ax2.legend() 129 | 130 | plt.tight_layout() 131 | plt.show() 132 | -------------------------------------------------------------------------------- /dev/concept2.py: -------------------------------------------------------------------------------- 1 | import scipy as sp 2 | import pylab as plt 3 | from scipy.integrate import odeint 4 | 5 | class HodgkinHuxley(): 6 | """Full Hodgkin-Huxley Model implemented in Python""" 7 | 8 | C_m = 1.0 9 | """membrane capacitance, in uF/cm^2""" 10 | 11 | g_Na = 120.0 12 | """Sodium (Na) maximum conductances, in mS/cm^2""" 13 | 14 | g_K = 36.0 15 | """Postassium (K) maximum conductances, in mS/cm^2""" 16 | 17 | g_L = 0.3 18 | """Leak maximum conductances, in mS/cm^2""" 19 | 20 | E_Na = 50.0 21 | """Sodium (Na) Nernst reversal potentials, in mV""" 22 | 23 | E_K = -77.0 24 | """Postassium (K) Nernst reversal potentials, in mV""" 25 | 26 | E_L = -54.387 27 | """Leak Nernst reversal potentials, in mV""" 28 | 29 | t = sp.arange(0.0, 450.0, 0.01) 30 | """ The time to integrate over """ 31 | 32 | def alpha_m(self, V): 33 | """Channel gating kinetics. Functions of membrane voltage""" 34 | return 0.1*(V+40.0)/(1.0 - sp.exp(-(V+40.0) / 10.0)) 35 | 36 | def beta_m(self, V): 37 | """Channel gating kinetics. Functions of membrane voltage""" 38 | return 4.0*sp.exp(-(V+65.0) / 18.0) 39 | 40 | def alpha_h(self, V): 41 | """Channel gating kinetics. Functions of membrane voltage""" 42 | return 0.07*sp.exp(-(V+65.0) / 20.0) 43 | 44 | def beta_h(self, V): 45 | """Channel gating kinetics. Functions of membrane voltage""" 46 | return 1.0/(1.0 + sp.exp(-(V+35.0) / 10.0)) 47 | 48 | def alpha_n(self, V): 49 | """Channel gating kinetics. Functions of membrane voltage""" 50 | return 0.01*(V+55.0)/(1.0 - sp.exp(-(V+55.0) / 10.0)) 51 | 52 | def beta_n(self, V): 53 | """Channel gating kinetics. Functions of membrane voltage""" 54 | return 0.125*sp.exp(-(V+65) / 80.0) 55 | 56 | def I_Na(self, V, m, h): 57 | """ 58 | Membrane current (in uA/cm^2) 59 | Sodium (Na = element name) 60 | 61 | | :param V: 62 | | :param m: 63 | | :param h: 64 | | :return: 65 | """ 66 | return self.g_Na * m**3 * h * (V - self.E_Na) 67 | 68 | def I_K(self, V, n): 69 | """ 70 | Membrane current (in uA/cm^2) 71 | Potassium (K = element name) 72 | 73 | | :param V: 74 | | :param h: 75 | | :return: 76 | """ 77 | return self.g_K * n**4 * (V - self.E_K) 78 | # Leak 79 | def I_L(self, V): 80 | """ 81 | Membrane current (in uA/cm^2) 82 | Leak 83 | 84 | | :param V: 85 | | :param h: 86 | | :return: 87 | """ 88 | return self.g_L * (V - self.E_L) 89 | 90 | def I_inj(self, t): 91 | """ 92 | External Current 93 | 94 | | :param t: time 95 | | :return: step up to 10 uA/cm^2 at t>100 96 | | step down to 0 uA/cm^2 at t>200 97 | | step up to 35 uA/cm^2 at t>300 98 | | step down to 0 uA/cm^2 at t>400 99 | """ 100 | return 10*(t>100) - 10*(t>200) + 35*(t>300) - 35*(t>400) 101 | 102 | @staticmethod 103 | def dALLdt(X, t, self): 104 | """ 105 | Integrate 106 | 107 | | :param X: 108 | | :param t: 109 | | :return: calculate membrane potential & activation variables 110 | """ 111 | V, m, h, n = X 112 | 113 | dVdt = (self.I_inj(t) - self.I_Na(V, m, h) - self.I_K(V, n) - self.I_L(V)) / self.C_m 114 | dmdt = self.alpha_m(V)*(1.0-m) - self.beta_m(V)*m 115 | dhdt = self.alpha_h(V)*(1.0-h) - self.beta_h(V)*h 116 | dndt = self.alpha_n(V)*(1.0-n) - self.beta_n(V)*n 117 | return dVdt, dmdt, dhdt, dndt 118 | 119 | def Main(self): 120 | """ 121 | Main demo for the Hodgkin Huxley neuron model 122 | """ 123 | 124 | X = odeint(self.dALLdt, [-65, 0.05, 0.6, 0.32], self.t, args=(self,)) 125 | V = X[:,0] 126 | m = X[:,1] 127 | h = X[:,2] 128 | n = X[:,3] 129 | ina = self.I_Na(V, m, h) 130 | ik = self.I_K(V, n) 131 | il = self.I_L(V) 132 | 133 | plt.figure() 134 | 135 | ax1 = plt.subplot(4,1,1) 136 | plt.title('Hodgkin-Huxley Neuron') 137 | plt.plot(self.t, V, 'k') 138 | plt.ylabel('V (mV)') 139 | 140 | plt.subplot(4,1,2, sharex = ax1) 141 | plt.plot(self.t, ina, 'c', label='$I_{Na}$') 142 | plt.plot(self.t, ik, 'y', label='$I_{K}$') 143 | plt.plot(self.t, il, 'm', label='$I_{L}$') 144 | plt.ylabel('Current') 145 | plt.legend() 146 | 147 | plt.subplot(4,1,3, sharex = ax1) 148 | plt.plot(self.t, m, 'r', label='m') 149 | plt.plot(self.t, h, 'g', label='h') 150 | plt.plot(self.t, n, 'b', label='n') 151 | plt.ylabel('Gating Value') 152 | plt.legend() 153 | 154 | plt.subplot(4,1,4, sharex = ax1) 155 | i_inj_values = [self.I_inj(t) for t in self.t] 156 | plt.plot(self.t, i_inj_values, 'k') 157 | plt.xlabel('t (ms)') 158 | plt.ylabel('$I_{inj}$ ($\\mu{A}/cm^2$)') 159 | plt.ylim(-1, 40) 160 | 161 | plt.tight_layout() 162 | plt.show() 163 | 164 | if __name__ == '__main__': 165 | runner = HodgkinHuxley() 166 | runner.Main() -------------------------------------------------------------------------------- /dev/concept3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/pyHH/61e4fa354f42f4bf3f30dd59cc697b9a2f30bdec/dev/concept3.png -------------------------------------------------------------------------------- /dev/concept3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hodgkin-Huxley Model Neuron Simulated Discretely with Python 3 | Inspired by matlab code on "andy's brain blog" Oct 15 2013. 4 | """ 5 | 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | 9 | # Simulation time (all time units are milliseconds) 10 | simulationTime = 200 11 | deltaT = .01 12 | pointsPerMillisec = 1.0/deltaT 13 | t = np.arange(simulationTime/deltaT) * deltaT 14 | 15 | # Create the stimulus waveform 16 | I = np.zeros(len(t)) 17 | 18 | # Add some square pulses to the stimulus waveform 19 | I[int(125 * pointsPerMillisec):int(175 * pointsPerMillisec)] = 50 20 | I[int(25 * pointsPerMillisec):int(75 * pointsPerMillisec)] = 10 21 | 22 | # channel conductances (mS/cm^2) 23 | gK = 36 24 | gNa = 120 25 | g_L = .3 26 | 27 | # ion reversal potentials (mV) 28 | E_K = -12 29 | E_Na = 115 30 | E_L = 10.6 31 | 32 | # membrane properties 33 | C = 1.0 # capacitance (uF/cm^2) 34 | 35 | # Open state over time (start at zero) 36 | n = np.zeros(len(t)) 37 | m = np.zeros(len(t)) 38 | h = np.zeros(len(t)) 39 | V = np.zeros(len(t)) 40 | 41 | # Initial States 42 | Vstart = -0 # Baseline voltage 43 | alpha_n = .01 * ((10-Vstart) / (np.exp((10-Vstart)/10)-1)) # Equation 12 44 | beta_n = .125*np.exp(-Vstart/80) # Equation 13 45 | alpha_m = .1*((25-Vstart) / (np.exp((25-Vstart)/10)-1)) # Equation 20 46 | beta_m = 4*np.exp(-Vstart/18) # Equation 21 47 | alpha_h = .07*np.exp(-Vstart/20) # Equation 23 48 | beta_h = 1/(np.exp((30-Vstart)/10)+1) # Equation 24 49 | 50 | # Initial conductances 51 | n[0] = alpha_n/(alpha_n+beta_n) # Equation 9 52 | m[0] = alpha_m/(alpha_m+beta_m) # Equation 18 53 | h[0] = alpha_h/(alpha_h+beta_h) # Equation 18 54 | V[0] = Vstart 55 | 56 | # Simulate 57 | for i in range(len(t)-1): 58 | 59 | # coefficients 60 | alpha_n = .01 * ((10-V[i]) / (np.exp((10-V[i])/10)-1)) 61 | beta_n = .125*np.exp(-V[i]/80) 62 | alpha_m = .1*((25-V[i]) / (np.exp((25-V[i])/10)-1)) 63 | beta_m = 4*np.exp(-V[i]/18) 64 | alpha_h = .07*np.exp(-V[i]/20) 65 | beta_h = 1/(np.exp((30-V[i])/10)+1) 66 | 67 | # currents 68 | I_Na = np.power(m[i], 3) * gNa * h[i] * (V[i]-E_Na) 69 | I_K = np.power(n[i], 4) * gK * (V[i]-E_K) 70 | I_L = g_L * (V[i]-E_L) 71 | I_ion = I[i] - I_K - I_Na - I_L 72 | 73 | # calculate derivatives using Euler first order approximation 74 | V[i+1] = V[i] + deltaT * I_ion / C 75 | n[i+1] = n[i] + deltaT * (alpha_n * (1-n[i]) - beta_n * n[i]) 76 | m[i+1] = m[i] + deltaT * (alpha_m * (1-m[i]) - beta_m * m[i]) 77 | h[i+1] = h[i] + deltaT * (alpha_h * (1-h[i]) - beta_h * h[i]) 78 | 79 | # Display Results 80 | V = V-70 # Set resting potential to -70mv 81 | 82 | plt.figure(figsize=(8, 8)) 83 | 84 | ax1 = plt.subplot(411) 85 | ax1.plot(t, V, color='b') 86 | ax1.set_ylabel("Potential (mV)") 87 | 88 | ax2 = plt.subplot(412) 89 | ax2.plot(t, I, color='r') 90 | ax2.set_ylabel("Stimulus") 91 | 92 | ax3 = plt.subplot(413, sharex=ax1) 93 | ax3.plot(t, gK*np.power(n, 4), label='K') 94 | ax3.plot(t, gNa*np.power(m, 3)*h, label='Na') 95 | ax3.set_ylabel("Conductance") 96 | plt.legend() 97 | 98 | ax4 = plt.subplot(414, sharex=ax1) 99 | ax4.plot(t, n, label='K') 100 | ax4.plot(t, m, label='Na') 101 | ax4.set_ylabel("Open State") 102 | ax4.set_xlabel("Time (milliseconds)") 103 | plt.legend() 104 | 105 | plt.tight_layout() 106 | plt.savefig("dev/concept3.png") 107 | plt.show() 108 | -------------------------------------------------------------------------------- /dev/concept4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/pyHH/61e4fa354f42f4bf3f30dd59cc697b9a2f30bdec/dev/concept4.png -------------------------------------------------------------------------------- /dev/concept4.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python implementation of the Hodgkin-Huxley spiking neuron model 3 | https://github.com/swharden/pyHH 4 | """ 5 | import matplotlib.pyplot as plt 6 | import numpy as np 7 | 8 | 9 | class HHModel: 10 | """The HHModel tracks conductances of 3 channels to calculate Vm""" 11 | 12 | class Gate: 13 | """The Gate object manages a channel's kinetics and open state""" 14 | alpha, beta, state = 0, 0, 0 15 | 16 | def update(self, deltaTms): 17 | alphaState = self.alpha * (1-self.state) 18 | betaState = self.beta * self.state 19 | self.state += deltaTms * (alphaState - betaState) 20 | 21 | def setInfiniteState(self): 22 | self.state = self.alpha / (self.alpha + self.beta) 23 | 24 | ENa, EK, EKleak = 115, -12, 10.6 25 | gNa, gK, gKleak = 120, 36, 0.3 26 | m, n, h = Gate(), Gate(), Gate() 27 | Cm = 1 28 | 29 | def __init__(self, startingVoltage=0): 30 | self.Vm = startingVoltage 31 | self.UpdateGateTimeConstants(startingVoltage) 32 | self.m.setInfiniteState() 33 | self.n.setInfiniteState() 34 | self.n.setInfiniteState() 35 | 36 | def UpdateGateTimeConstants(self, Vm): 37 | """Update time constants of all gates based on the given Vm""" 38 | self.n.alpha = .01 * ((10-Vm) / (np.exp((10-Vm)/10)-1)) 39 | self.n.beta = .125*np.exp(-Vm/80) 40 | self.m.alpha = .1*((25-Vm) / (np.exp((25-Vm)/10)-1)) 41 | self.m.beta = 4*np.exp(-Vm/18) 42 | self.h.alpha = .07*np.exp(-Vm/20) 43 | self.h.beta = 1/(np.exp((30-Vm)/10)+1) 44 | 45 | def UpdateCellVoltage(self, stimulusCurrent, deltaTms): 46 | """calculate channel currents using the latest gate time constants""" 47 | INa = np.power(self.m.state, 3) * self.gNa * \ 48 | self.h.state*(self.Vm-self.ENa) 49 | IK = np.power(self.n.state, 4) * self.gK * (self.Vm-self.EK) 50 | IKleak = self.gKleak * (self.Vm-self.EKleak) 51 | Isum = stimulusCurrent - INa - IK - IKleak 52 | self.Vm += deltaTms * Isum / self.Cm 53 | 54 | def UpdateGateStates(self, deltaTms): 55 | """calculate new channel open states using latest Vm""" 56 | self.n.update(deltaTms) 57 | self.m.update(deltaTms) 58 | self.h.update(deltaTms) 59 | 60 | def Iterate(self, stimulusCurrent=0, deltaTms=0.05): 61 | self.UpdateGateTimeConstants(self.Vm) 62 | self.UpdateCellVoltage(stimulusCurrent, deltaTms) 63 | self.UpdateGateStates(deltaTms) 64 | 65 | 66 | if __name__ == "__main__": 67 | hh = HHModel() 68 | pointCount = 5000 69 | voltages = np.empty(pointCount) 70 | times = np.arange(pointCount) * 0.05 71 | stim = np.zeros(pointCount) 72 | stim[1200:3800] = 20 # create a square pulse 73 | 74 | for i in range(len(times)): 75 | hh.Iterate(stimulusCurrent=stim[i], deltaTms=0.05) 76 | voltages[i] = hh.Vm 77 | # note: you could also plot hh's n, m, and k (channel open states) 78 | 79 | f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(8, 5), 80 | gridspec_kw={'height_ratios': [3, 1]}) 81 | 82 | ax1.plot(times, voltages - 70, 'b') 83 | ax1.set_ylabel("Membrane Potential (mV)") 84 | ax1.set_title("Hodgkin-Huxley Spiking Neuron Model", fontSize=16) 85 | ax1.spines['right'].set_visible(False) 86 | ax1.spines['top'].set_visible(False) 87 | ax1.spines['bottom'].set_visible(False) 88 | ax1.tick_params(bottom=False) 89 | 90 | ax2.plot(times, stim, 'r') 91 | ax2.set_ylabel("Stimulus (µA/cm²)") 92 | ax2.set_xlabel("Simulation Time (milliseconds)") 93 | ax2.spines['right'].set_visible(False) 94 | ax2.spines['top'].set_visible(False) 95 | 96 | plt.margins(0, 0.1) 97 | plt.tight_layout() 98 | plt.savefig("dev/concept4.png") 99 | plt.show() 100 | -------------------------------------------------------------------------------- /dev/figures/320px-Hodgkin-Huxley.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/pyHH/61e4fa354f42f4bf3f30dd59cc697b9a2f30bdec/dev/figures/320px-Hodgkin-Huxley.svg.png -------------------------------------------------------------------------------- /dev/figures/800px-Hodgkin-Huxley.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/pyHH/61e4fa354f42f4bf3f30dd59cc697b9a2f30bdec/dev/figures/800px-Hodgkin-Huxley.svg.png -------------------------------------------------------------------------------- /src/pyhh/__init__.py: -------------------------------------------------------------------------------- 1 | from pyhh.models import HHModel 2 | from pyhh.simulations import Simulation -------------------------------------------------------------------------------- /src/pyhh/models.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class HHModel: 5 | """The HHModel tracks conductances of 3 channels to calculate Vm""" 6 | 7 | class Gate: 8 | """The Gate object manages a channel's kinetics and open state""" 9 | alpha, beta, state = 0, 0, 0 10 | 11 | def update(self, deltaTms): 12 | alphaState = self.alpha * (1-self.state) 13 | betaState = self.beta * self.state 14 | self.state += deltaTms * (alphaState - betaState) 15 | 16 | def setInfiniteState(self): 17 | self.state = self.alpha / (self.alpha + self.beta) 18 | 19 | ENa, EK, EKleak = 115, -12, 10.6 20 | gNa, gK, gKleak = 120, 36, 0.3 21 | m, n, h = Gate(), Gate(), Gate() 22 | Cm = 1 23 | 24 | def __init__(self, startingVoltage=0): 25 | self.Vm = startingVoltage 26 | self._UpdateGateTimeConstants(startingVoltage) 27 | self.m.setInfiniteState() 28 | self.n.setInfiniteState() 29 | self.n.setInfiniteState() 30 | 31 | def _UpdateGateTimeConstants(self, Vm): 32 | """Update time constants of all gates based on the given Vm""" 33 | self.n.alpha = .01 * ((10-Vm) / (np.exp((10-Vm)/10)-1)) 34 | self.n.beta = .125*np.exp(-Vm/80) 35 | self.m.alpha = .1*((25-Vm) / (np.exp((25-Vm)/10)-1)) 36 | self.m.beta = 4*np.exp(-Vm/18) 37 | self.h.alpha = .07*np.exp(-Vm/20) 38 | self.h.beta = 1/(np.exp((30-Vm)/10)+1) 39 | 40 | def _UpdateCellVoltage(self, stimulusCurrent, deltaTms): 41 | """calculate channel currents using the latest gate time constants""" 42 | self.INa = np.power(self.m.state, 3) * self.gNa * \ 43 | self.h.state*(self.Vm-self.ENa) 44 | self.IK = np.power(self.n.state, 4) * self.gK * (self.Vm-self.EK) 45 | self.IKleak = self.gKleak * (self.Vm-self.EKleak) 46 | Isum = stimulusCurrent - self.INa - self.IK - self.IKleak 47 | self.Vm += deltaTms * Isum / self.Cm 48 | 49 | def _UpdateGateStates(self, deltaTms): 50 | """calculate new channel open states using latest Vm""" 51 | self.n.update(deltaTms) 52 | self.m.update(deltaTms) 53 | self.h.update(deltaTms) 54 | 55 | def iterate(self, stimulusCurrent, deltaTms): 56 | self._UpdateGateTimeConstants(self.Vm) 57 | self._UpdateCellVoltage(stimulusCurrent, deltaTms) 58 | self._UpdateGateStates(deltaTms) 59 | -------------------------------------------------------------------------------- /src/pyhh/simulations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import warnings 3 | 4 | 5 | class Simulation: 6 | 7 | def __init__(self, model): 8 | self.model = model 9 | self.CreateArrays(0, 0) 10 | pass 11 | 12 | def CreateArrays(self, pointCount, deltaTms): 13 | self.times = np.arange(pointCount) * deltaTms 14 | self.Vm = np.empty(pointCount) 15 | self.INa = np.empty(pointCount) 16 | self.IK = np.empty(pointCount) 17 | self.IKleak = np.empty(pointCount) 18 | self.StateN = np.empty(pointCount) 19 | self.StateM = np.empty(pointCount) 20 | self.StateH = np.empty(pointCount) 21 | 22 | def Run(self, stimulusWaveform, stepSizeMs): 23 | if (stepSizeMs > 0.05): 24 | warnings.warn("step sizes < 0.05 ms are recommended") 25 | assert isinstance(stimulusWaveform, np.ndarray) 26 | self.CreateArrays(len(stimulusWaveform), stepSizeMs) 27 | print(f"simulating {len(stimulusWaveform)} time points...") 28 | for i in range(len(stimulusWaveform)): 29 | self.model.iterate(stimulusWaveform[i], stepSizeMs) 30 | self.Vm[i] = self.model.Vm 31 | self.INa[i] = self.model.INa 32 | self.IK[i] = self.model.IK 33 | self.IKleak[i] = self.model.IKleak 34 | self.StateH[i] = self.model.h.state 35 | self.StateM[i] = self.model.m.state 36 | self.StateN[i] = self.model.n.state 37 | print("simulation complete") 38 | 39 | 40 | assert __name__ != "__main__", "do not execute this module (only import it)" 41 | -------------------------------------------------------------------------------- /tests/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swharden/pyHH/61e4fa354f42f4bf3f30dd59cc697b9a2f30bdec/tests/demo.png -------------------------------------------------------------------------------- /tests/demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | if not os.path.exists("./src/pyhh/__init__.py"): 4 | raise Exception("script must be run in project root folder") 5 | sys.path.append("./src") 6 | 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import pyhh 10 | 11 | if __name__ == "__main__": 12 | 13 | # customize a neuron model if desired 14 | model = pyhh.HHModel() 15 | model.gNa = 100 # typically 120 16 | model.gK = 5 # typically 36 17 | model.EK = -35 # typically -12 18 | 19 | # customize a stimulus waveform 20 | stim = np.zeros(20000) 21 | stim[7000:13000] = 50 # add a square pulse 22 | 23 | # simulate the model cell using the custom waveform 24 | sim = pyhh.Simulation(model) 25 | sim.Run(stimulusWaveform=stim, stepSizeMs=0.01) 26 | 27 | # plot the results with MatPlotLib 28 | plt.figure(figsize=(10, 8)) 29 | 30 | ax1 = plt.subplot(411) 31 | ax1.plot(sim.times, sim.Vm - 70, color='b') 32 | ax1.set_ylabel("Potential (mV)") 33 | ax1.set_title("Hodgkin-Huxley Spiking Neuron Model", fontSize=16) 34 | 35 | ax2 = plt.subplot(412) 36 | ax2.plot(sim.times, stim, color='r') 37 | ax2.set_ylabel("Stimulation (µA/cm²)") 38 | 39 | ax3 = plt.subplot(413, sharex=ax1) 40 | ax3.plot(sim.times, sim.StateH, label='h') 41 | ax3.plot(sim.times, sim.StateM, label='m') 42 | ax3.plot(sim.times, sim.StateN, label='n') 43 | ax3.set_ylabel("Activation (frac)") 44 | ax3.legend() 45 | 46 | ax4 = plt.subplot(414, sharex=ax1) 47 | ax4.plot(sim.times, sim.INa, label='VGSC') 48 | ax4.plot(sim.times, sim.IK, label='VGKC') 49 | ax4.plot(sim.times, sim.IKleak, label='KLeak') 50 | ax4.set_ylabel("Current (µA/cm²)") 51 | ax4.set_xlabel("Simulation Time (milliseconds)") 52 | ax4.legend() 53 | 54 | plt.tight_layout() 55 | plt.savefig("tests/demo.png") 56 | plt.show() 57 | --------------------------------------------------------------------------------