
zunda framework チュートリアル
さいしょに
まず,あたらしいプロジェクトを作成して,フレームワークのDLLかプロジェクトを参照に追加します。このチュートリアルでは,Hodgkin-Huxleyモデルを構築します。
膜電位V は,膜容量CMと入力電流IM,ナトリウム電流INa,カリウム電流IK,リーク電流IL によって次式で表されます。INa,IK,IL は,それぞれ最大コンダクタンスgと平衡電位V まず,リークチャネルを作成します。チャネルは リークチャネルは,コンダクタンスと平衡電位で次のように記述できますIl = -gl (Vm - Vl)。ここでは,コンダクタンスは0.3mS/cm2,平衡電位は-49.4mVとします。チャネルで流れる電流は つぎにカリウムチャネルのクラスを作ります。これも同じように カリウムチャネルの電流は,平衡電位VKと膜電位依存性活性化変数nによって式(3)によって表されます。ここでは,nは時間で微分する必要があるので
あとは,リーク電流と同じように
最後にナトリウムチャネルを作ります。カリウムチャネルと同様に膜電位依存性の活性化変数のmがあります。ただし,ここではもう一つ膜電位依存性の不活性変数も必要です。微分する変数がもう一つ増えたのでコンストラクタで追加する変数も同じように増やします。 チャネルができて準備ができたのでいよいよニューロンを作ります。 つぎにさっき作ったチャネルのインスタンスを作成します。インスタンスを作るときに現在のニューロンをコンストラクタに設定することでチャネルの関連付けは完了します。 つぎに膜容量や温度定数,各チャネルの開口率などを初期化して ほかに外部からチャネルの状態を見るためにアクセサなどを設定してもいいかもしれません。 式(1)のIMの外部からの入力電流は,ここでは定義せずメソッドのみ追加しておきます。 さあ,これで準備が全て整いました。シミュレーション呼び出すメインを作っていきます。コードのキモは,Calculatorで微分計算機を初期化してそれに,新しいニューロンのインスタンスを追加します。そして,そこに刺激入力を定義し,メインループで回して行きます。ログの取得や発火率の計算もできるようになってるのでサンプルファイルを見ながらやってみてね!チャネルを作成する
リークチャネル電流
CurrentChannel
クラスを継承して作ります。GetCurrent
メソッドをオーバーライドすることで指定します。例では,Enable
を外部で指定してチャネルの有効化を設定できるようにしてあります。
/// <summary>
/// リーク電流
/// </summary>
public class LeakCurrent : CurrentChannel {
/// <summary>
/// LeakCurrentインスタンスを初期化します.
/// </summary>
/// <param name="selfNeuron"></param>
public LeakCurrent(Neuron selfNeuron)
: base(selfNeuron) {
Conductance = 0.3;
ReversalPotential = -49.4;
}
/// <summary>
/// リーク電流
/// </summary>
/// <returns></returns>
public override double GetCurrent() {
if(Enable) {
return -Conductance * (SelfNeuron.Potential - ReversalPotential);
} else {
return 0.0;
}
}
}
カリウムチャネル電流
CurrentChanel
クラスから継承してチャネルを作ります。Neuron
クラスにある微分変数リストへ式と共に登録する必要があります。登録はAddODE
メソッドで行い,ODEActivateKのメソッドを(デリゲートまはたラムダ式でもよい)を初期値と名前とともに渡します。GetCurrent
メソッドをオーバーライドしてカリウム電流を取得する式を記述します
public class KCurrent : CurrentChannel {
private int activateIdx;
/// <summary>
/// インスタンスの初期化.
/// </summary>
/// <param name="selfNeuron"></param>
public KCurrent(Neuron selfNeuron)
: base(selfNeuron) {
Conductance = 36.0;
ReversalPotential = -72.0;
this.activateIdx = selfNeuron.Length;
selfNeuron.AddODE(ODEActivateK, 0.0, "ActivateK");
Length = 1;
}
/// <summary>
/// 活性化変数の取得と設定をします.
/// </summary>
public double ActivateK {
get { return SelfNeuron[activateIdx]; }
set { SelfNeuron[activateIdx] = value; }
}
/// <summary>
/// 微分方程式:K活性化変数n
/// </summary>
/// <returns></returns>
public double ODEActivateK() {
double alpha, beta;
alpha = (-0.01 * (SelfNeuron.Potential + 50)) / (-1 + Math.Exp(-(SelfNeuron.Potential + 50) / 10));
beta = 0.125 * Math.Exp(-(SelfNeuron.Potential + 60) / 80);
return SelfNeuron.Temperature * (alpha * (1 - ActivateK) - beta * ActivateK);
}
/// <summary>
/// カリウム電流
/// </summary>
/// <returns></returns>
public override double GetCurrent() {
if(Enable) {
return -Conductance * ActivateK * ActivateK * ActivateK * ActivateK * (SelfNeuron.Potential - ReversalPotential);
} else {
return 0.0;
}
}
}
ナトリウムチャネル電流
public class NaCurrent : CurrentChannel {
private int activateIdx, inactivateIdx;
/// <summary>
/// インスタンスの初期化.
/// </summary>
/// <param name="selfNeuron"></param>
public NaCurrent(Neuron selfNeuron)
: base(selfNeuron) {
Conductance = 120.0;
ReversalPotential = 55.0;
this.activateIdx = selfNeuron.Length;
selfNeuron.AddODE(ODEActivateNa, 0.0, "ActivateNa");
this.inactivateIdx = selfNeuron.Length;
selfNeuron.AddODE(ODEInactivateNa, 0.0, "InactivateNa");
Length = 2;
}
/// <summary>
/// 活性化変数の取得と設定.
/// </summary>
public double ActivateNa {
get { return SelfNeuron[activateIdx]; }
set { SelfNeuron[activateIdx] = value; }
}
/// <summary>
/// 不活性化変数の取得と設定.
/// </summary>
public double InactivateNa {
get { return SelfNeuron[inactivateIdx]; }
set { SelfNeuron[inactivateIdx] = value; }
}
/// <summary>
/// 微分方程式:Na活性化変数m
/// </summary>
/// <returns></returns>
public double ODEActivateNa() {
double alpha, beta;
alpha = (-0.1 * (SelfNeuron.Potential + 35)) / (-1 + Math.Exp(-(SelfNeuron.Potential + 35) / 10));
beta = 4 * Math.Exp(-(SelfNeuron.Potential + 60) / 18);
return SelfNeuron.Temperature * (alpha * (1 - ActivateNa) - beta * ActivateNa);
}
/// <summary>
/// 微分方程式:Na不活性化変数h
/// </summary>
/// <returns></returns>
public double ODEInactivateNa() {
double alpha, beta;
alpha = 0.07 * Math.Exp(-(SelfNeuron.Potential + 60) / 20);
beta = 1 / (1 + Math.Exp(-(SelfNeuron.Potential + 30) / 10));
return SelfNeuron.Temperature * (alpha * (1 - InactivateNa) - beta * InactivateNa);
}
/// <summary>
/// ナトリウム電流
/// </summary>
/// <returns></returns>
public override double GetCurrent() {
if(Enable) {
return -Conductance * ActivateNa * ActivateNa * ActivateNa * InactivateNa * (SelfNeuron.Potential - ReversalPotential);
} else {
return 0.0;
}
}
}
ニューロンを作る
Neuron
クラスから継承します。まず膜電位を微分する必要があるのでODEPotential
を微分変数に追加します。ODEPotential
は式(1)がすでに定義されていて,各電流は追加したチャネルから取得されます。AddCurrent
で式(1)の電流を設定します。
public class HHNeuron : Neuron {
private LeakCurrent leak;
private NaCurrent na;
private KCurrent k;
/// <summary>
/// Hodgkin Huxley ニューロンモデルを作成します.
/// </summary>
public HHNeuron() {
//膜電位微分方程式を追加します
AddODE(ODEPotential, -60.0, "Potential"); //potential
//各チャンネルを作ります
leak = new LeakCurrent(this);
na = new NaCurrent(this);
k = new KCurrent(this);
//膜容量・温度定数の初期化
//初期化しない場合は1.0が代入されます
Capacitance = 1.0;
Temperature = 1.0;
//各チャンネルの開口率などの初期化
na.ActivateNa = 0.0529342;
na.InactivateNa = 0.596111;
k.ActivateK = 0.317682;
//膜電位微分方程式に各チャンネルの電流を追加します
AddCurrent(na.GetCurrent, k.GetCurrent, leak.GetCurrent, GetStimulusCurrent);
}
#region 各チャンネルのアクセサ
/// <summary>
/// リークチャンネルを取得します.
/// </summary>
public LeakCurrent Leak { get { return leak; } }
/// <summary>
/// Naチャンネルを取得します.
/// </summary>
public NaCurrent NaChannel { get { return na; } }
/// <summary>
/// Kチャンネルを取得します.
/// </summary>
public KCurrent KChannel { get { return k; } }
#endregion
}
シミュレーションする
static void Main(string[] args) {
SimulationLog simlog = new SimulationLog("simlog.txt"); //計算時間などのログを取ります
Calculator calc = new Calculator(ODEType.RungeKutta4);//微分方程式の計算方法を指定します
SimulationTime.Delta = 0.005;//計算のきざみ幅を設定します
SimulationTime.EndTime = 500;//終了時間を設定します
SimulationTime.Interval = 50;//出力ファイルのインターバルを設定します
ProgressBar pb = new ProgressBar(ProgressBar.Type.Percents);//プログレスバーを表示するようにします
HHNeuron n = new HHNeuron();//あたらしいHHNeuronを作ります.
n.StimulusCurrentFunction = delegate(double t) {//刺激入力を定義します
if(10 < t && t <= 210) {
return 7.0;
} else if(210 < t && t <= 410) {
return 18.0;
} else {
return 0.0;
}
};
MeasureFiringRate fr = new MeasureFiringRate(n);//nの発火率を計測するfrを作ります.
calc.AddNeuron(n);//微分計算機にnを追加します
try {//計算途中に値がトンだ場合に備えて
//出力ファイル名の設定をします
using(DataWriterBinder logs = new DataWriterBinder("data0.txt", "data1.txt", "rate.txt")) {
while(SimulationTime.IsSimulation()) {//メインループ
//ログ書き込みのメソッドは下記のWriteとかWriteWithTime以外にもいろいろあるので
//試してみてね
logs[0].WriteWithTime(n.Potential, n.NaChannel.ActivateNa);//ログの書き込み
logs[1].Write(n);//ログの書き込み
logs[2].WriteAsyncTimeData(fr.GetFiringRate());//発火率の書き込み
calc.Calc();//計算を1ステップ進めます
}
}
} catch(InvalidOperationException ex) {//値が飛んだ場合は強制終了
Console.Error.Write(ex.ToString());
}
pb.Stop();//プログレスバーを停止します.
simlog.Finish(calc);//シミュレーションのログを取ります
}