Simulieren Sie ein Modellneuron

16

Ein Izhikevich-Neuron ist ein einfaches, aber sehr effektives Modell eines biologischen Neurons, das für die Verwendung in einer diskreten Zeitschritt-Simulation entwickelt wurde. In dieser Golfherausforderung implementieren Sie dieses Modell.

Parameter

Dieses Modell umfasst nur 7 Variablen, die in 2 Differentialgleichungen organisiert sind, verglichen mit den Dutzenden von Parametern eines physiologisch genauen Modells.

  • vund usind die beiden Zustandsvariablen des Neurons. Hierbei vhandelt es sich um die "schnelle" Variable, die das Zellpotential über die Zeit darstellt, und uum die "langsame" Variable, die bestimmte Membraneigenschaften darstellt. Die vVariable ist die wichtigste, da dies die Ausgabe der Simulation ist.
  • a, b, c, Und dsind Konstanten festgelegt, die die Eigenschaften des Neurons beschreiben. Verschiedene Arten von Neuronen haben je nach gewünschtem Verhalten unterschiedliche Konstanten. Bemerkenswert cist das Rücksetzpotential, dh das Membranpotential, auf das die Zelle nach dem Aufstocken zurückkehrt.
  • Irepräsentiert den Eingangsstrom zum Neuron. In Netzwerksimulationen wird sich dies mit der Zeit ändern, aber für unsere Zwecke werden wir dies Ials feste Konstante behandeln.

Das Model

Dieses Modell hat einen sehr einfachen Pseudocode. Zunächst nehmen wir die konstanten Werte von abcdund verwenden sie zur Initialisierung vund u:

v = c
u = b * c

Als nächstes durchlaufen wir den Simulationscode so oft wie gewünscht. Jede Iteration entspricht einer Millisekunde.

for 1..t:
  if v >= 30:    # reset after a spike
    v = c
    u = u + d
  v += 0.04*v^2 + 5*v + 140 - u + I
  u += a * (b*v - u)
  print v

Bestimmte Real-World-Implementierungen enthalten zusätzliche Schritte für die numerische Genauigkeit, die hier jedoch nicht berücksichtigt werden.

Eingang

Als Eingabe Ihr Programm / Funktion die Werte nehmen soll a, b, c, d, I, und t(die Anzahl der Zeitschritte zu simulieren). Einmal eingestellt, ändert sich keiner dieser Parameter während unserer einfachen Simulation. Die Reihenfolge der Eingabe spielt keine Rolle: Sie können die Reihenfolge angeben, in der Ihr Programm diese Parameter verwendet.

Ausgabe

Es wird eine Liste von Zahlen ausgegeben, die das Membranpotential der Zelle (gegeben durch Variable v) im Verlauf der Simulation darstellen. Die Liste kann in jedem geeigneten Format vorliegen.

Sie haben die Wahl, ob Sie den 0. Wert der Simulation (die Erstkonfiguration, bevor eine Zeit verstrichen ist) in Ihre Ausgabe einbeziehen möchten. Zum Beispiel für eine Eingabe von 0.02 0.2 -50 2 10 6(für a b c d I t) eine Ausgabe von entweder

-50
-40
-16.04
73.876224
-42.667044096
-25.8262335380956
29.0355029192068

oder

-40
-16.04
73.876224
-42.667044096
-25.8262335380956
29.0355029192068

ist akzeptabel.

Ihre Werte müssen nicht exakt mit den oben genannten Werten übereinstimmen , je nachdem, wie Ihre Sprache mit Gleitkommazahlen umgeht.

Referenzimplementierung

Hier ist eine TIO-Implementierung, die ich in Perl geschrieben habe, um das Modell zu demonstrieren. Die Parameter sind die eines "klappernden" Neurons aus dem oben verlinkten Artikel, und dies dient als Demonstration, wie dieses Modell einige der komplexeren Eigenschaften von Neuronen wiederherstellen kann, wie z. B. das Wechseln zwischen Zuständen hoher und niedriger Aktivität. Wenn Sie sich den Ausgang ansehen, können Sie sehen, wo das Neuron sofort mehrere Male spitzt, aber dann eine Weile wartet, bevor es mehrere Male spitzt (obwohl die Zelleingangsspannung Idie ganze Zeit konstant ist).

PhiNotPi
quelle
Wird tjemals negativ sein?
Kamoroso94
1
@ kamoroso94 Nein, du kannst keine negative Zeit simulieren.
PhiNotPi

Antworten:

6

R , 110 99 Bytes

Anonyme Funktion, die 6 Argumente akzeptiert. Nichts Besonderes, nur eine einfache Portierung der Referenzimplementierung. Die Aktualisierung vonu ,v und das Drucken von vwurden alle in einer einzigen Zeile zusammengefasst, da R den printzu druckenden Wert zurückgibt, sodass Sie ihn bei der Zuweisung verwenden können. Vielen Dank an Giuseppe für das Speichern von 11 Bytes!

pryr::f({v=c;u=b*c;for(i in 1:t){if(v>=30){v=c;u=u+d}
u=a*b*(v=print((.04*v+6)*v+140+I-u))-a*u+u}})

Probieren Sie es online!

rturnbull
quelle
2
Das ist großartig, +1. Da Sie die Argumente jedoch explizit kennzeichnen, wird zwischen pryr::f()und kein Byte gespeichert function(). Sie können jedoch nach einigen Experimenten bewegen vund u‚s Erklärungen in den Funktionskörper , während die Reihenfolge der Argumente zu bewahren, ein Dutzend speichern Bytes Online ausprobieren!
Giuseppe
da vnicht unbedingt ganzzahlige Werte verwendet werden müssen v>=30, ist dies jedoch erforderlich
Giuseppe
@ Giuseppe Danke, diese Verbesserungen sind fantastisch. Aus irgendeinem Grund hatte ich nicht daran
gedacht,
4

Sauber , 150 145 140 138 Bytes

import StdEnv
$a b c d i t=map snd(iterate(\(u,v)#(w,n)=if(30.0<v)(c,u+d)(v,u)
#y=0.04*w*w+6.0*w+140.0-n+i
=(a*b*y-a*n+n,y))(b*c,c))%(0,t)

Probieren Sie es online!

Definiert die Funktion $ :: Real Real Real Real Real Int -> [Real], die den Algorithmus wie im OP beschrieben ab dem 0. Term implementiert.

Οurous
quelle
3

Python 2 , 100 Bytes

a,b,c,d,I,t=input();v=c;u=b*c
exec"if v>=30:v=c;u+=d\nv=v*v/25+6*v+140-u+I;u+=a*(b*v-u);print v\n"*t

Probieren Sie es online!

2 Bytes dank user71546 gespeichert .

Mr. Xcoder
quelle
@ovs Ups, du hast recht. Sollte jetzt behoben sein.
Mr. Xcoder
Wenn Sie 0.04*v*vzu wechseln , v*v/25.sollte 1 Byte gespart werden. Wenn immer für floats angegeben cwird, v*v/25reicht das für -2 Bytes.
Shieru Asakoto
@ceilingcat Wenn Sie sich mein Revisionsprotokoll ansehen, werden Sie feststellen, dass ich es v>29in meiner ursprünglichen Version hatte. Dies ist jedoch ungültig, da ves sich nicht unbedingt um eine Ganzzahl handelt.
Mr. Xcoder
3

JavaScript (Node.js) , 107 ... 103 101 Bytes

Beigetragen von @apsillers

(a,b,c,d,I,t)=>[...Array(t)].map(_=>(v<30||(v=c,u+=d),v=v*(v/25+6)+140-u+I,u+=a*(b*v-u),v),u=b*(v=c))

Probieren Sie es online!

Ursprünglicher Ansatz: 105 103 Bytes. -1 Byte Danke Arnauld und -2 Byte Danke @ Kamoroso94.

(a,b,c,d,I,t)=>{for(u=b*(v=c);t--;){v<30||(v=c,u+=d);v=v*(v/25+6)+140-u+I;u+=a*(b*v-u);console.log(v)}}

Probieren Sie es online!

Oder wenn das Poppen von Warnungen in Ordnung ist, dann 101 ... 99 97 Bytes (-1 Bytes Danke Arnauld, -2 Bytes Danke @ Kamoroso94):

(a,b,c,d,I,t)=>{for(u=b*(v=c);t--;){v<30||(v=c,u+=d);v=v*(v/25+6)+140-u+I;u+=a*(b*v-u);alert(v)}}

var u, v;
var f = 
(a,b,c,d,I,t)=>{for(u=b*(v=c);t--;){v<30||(v=c,u+=d);v=v*(v/25+6)+140-u+I;u+=a*(b*v-u);alert(v)}}

function run() {
 f(...["a", "b", "c", "d", "I", "t"].map(x => document.getElementById(x).value * 1));
}
a = <input id="a" value="0.02"><br>
b = <input id="b" value="0.2"><br>
c = <input id="c" value="-50"><br>
d = <input id="d" value="2"><br>
I = <input id="I" value="10"><br>
t = <input id="t" value="6"><br>
<input type="button" value="Run" onclick="run()">

Shieru Asakoto
quelle
v>29ist nicht gleichbedeutend mit v>=30für Schwimmer. Sie möchten wahrscheinlich v<30?0:(v=c,u+=d)stattdessen oder noch besser, v<30||(v=c,u+=d)was ein Byte speichert.
Arnauld
@Arnauld Oh ja, als ich mir die Python-Antwort ansah, wurde mir klar, dass ich das nicht optimiert habe, aber ich wusste auch nicht, dass ich Floats verarbeite.; P Behoben.
Shieru Asakoto
2
Sie können zwei Bytes sparen, indem Sie t-->0zu einfach wechseln t--.
Kamoroso94
1
Sie können diese bis zu 101 erhalten , indem die Refactoring - forSchleife in einen mapBetrieb auf einer Anordnung von Länge t: (a,b,c,d,I,t)=>[...Array(t)].map(_=>(v<30||(v=c,u+=d),v=v*(v/25+6)+140-u+I,u+=a*(b*v-u),v),u=b*(v=c)). Die Funktion gibt ein Array zurück, anstatt Werte zu protokollieren, die anscheinend der Spezifikation entsprechen. Aber die alertLösung ist nicht zu schlagen .
Apsillers
2

Ruby , 94 Bytes

->a,b,c,d,i,t{v=c
u=b*c
t.times{v>=30?(v=c;u+=d):0
v+=0.04*v**2+5*v+140-u+i
u+=a*(b*v-u)
p v}}

Probieren Sie es online!

Ein weiterer einfacher Port der Referenzimplementierung: Ein Lambda, das 6 Argumente akzeptiert.

benj2240
quelle
2

Haskell , 112 111 Bytes

(a#b)c d i t|let r(v,u)|v>=30=r(c,u+d)|p<-0.04*v^2+6*v+140-u+i=(p,u+a*(b*p-u))=fst<$>take t(iterate r$r(c,b*c))

Probieren Sie es online!

Gibt den Nullfall nicht aus. Nimmt an, dass dies cniemals der Fall ist, >=30da dies keinen Sinn ergibt .

Ich hätte nie gedacht, dass ich eine whereKlausel in einem Codegolf verwenden muss, aber es gibt einfach zu viele Variablen.

EDIT: Danke @Lynn für das Abheben eines Bytes! Ich habe vergessen, dass Sie letAussagen in Wachen setzen können. Klar, aber die Lesbarkeit ist nicht gut

user1472751
quelle
1
Sie können die wheredurch die seltsame f x|let g a=b=ySyntax ersetzen , um ein Byte zu speichern:(a#b)c d i t|let r(v,u)|v>=30=r(c,u+d)|p<-0.04*v^2+6*v+140-u+i=(p,u+a*(b*p-u))=fst<$>take t(iterate r$r(c,b*c))
Lynn
1

Element , 81 Bytes

_a;_b;_3:b~*u;_d;_I;_'[3:\.04*5+*140+u~-+I~++4:\
.`30<!b~*u~-+a~*u~+[d~+]u;[#2:]]

Probieren Sie es online! , Esolangs Seite

Erläuterung:

_a;_b;_3:b~*u;_d;_I;_'[ ... ]

Dieser Teil des Programms nimmt Eingaben vor. Es speichert Konstanten a, b, d, und Iin Variablen. Die Eingabe für cwird nie in einer Variablen gespeichert, sondern verbleibt während der Ausführung auf dem Hauptstapel. Es werden drei Kopien erstellt: eine oben zum Initialisieren u, eine in der Mitte als Initiale vund eine unten als Konstante c. Die Eingabe für twird sofort auf den Steuerstapel geworfen, um als Grundlage für die FOR-Schleife (die FOR-Schleife [...]) zu dienen, die den Rest des Programms umgibt.

3:\.04*5+*140+u~-+I~++4:

Dieser Teil des Programms nimmt den aktuellen Wert von vund berechnet den neuen Wert. Anschließend werden vier Kopien des neuen vWerts erstellt.

\
.`

Die erste Kopie von vhat einen Zeilenvorschub angehängt und wird gedruckt.

30<!

Die zweite Kopie von vwird verwendet, um zu testen, ob das Neuron einen Spitzenwert aufweist. Das Ergebnis dieses Tests wird zur späteren Verwendung auf dem Kontrollstapel abgelegt.

b~*u~-+a~*u~+

Dieser Teil berechnet das "Delta u", dh den Betrag, zu dem hinzugefügt werden soll u.

[d~+]

Dieser IF-Block addiert sich dzu der obigen Summe, wenn das Neuron spitzt. Dadurch werden normalerweise zwei Aufgaben zu einer einzigen Aufgabe zusammengefasst.

u;

Hier wird der aktualisierte Wert von gespeichert u.

[#2:]

Dieser IF-Block ist eine Fortsetzung des obigen IF-Blocks. Wenn das Neuron einen Spitzenwert aufweist, löschen Sie den aktuellen Wert von v(der sich jetzt oben auf dem Hauptstapel befindet) und ersetzen Sie ihn durch ein Duplikat vonc (das sich die ganze Zeit unten auf dem Hauptstapel befunden hat).

Und das ist im Grunde alles, was dazu gehört. Eine kleine Anmerkung ist, dass diese Sache Gedächtnis verliert: es nimmt ein Extra"# , um die Oberseite des Kontrollstapels (die ausgewertete IF-Bedingung) nach jeder Schleifeniteration zu löschen.

Obwohl ich Element nicht als die eleganteste Golfsprache bezeichnen würde, kann ich mit dieser Herausforderung ein interessantes Feature präsentieren: Aufgrund der Aufteilung zwischen Hauptstapel und Kontrollstapel kann ich eine IF-Anweisung nehmen und die Bedingung und den Körper in mehrere aufteilen Teile, mit unbedingtem Code verschachtelt.

PhiNotPi
quelle
0

MATLAB, 111 Bytes

function z(a,b,c,d,I,t)
v=c;u=b*c;for i=1:t if v>=30 v=c;u=u+d;end
v=.04*v^2+6*v+140-u+I
u=u+a*(b*v-u);
end
end

Ziemlich einfache Implementierung, kann wahrscheinlich weiter verbessert werden.

Chris Loonam
quelle