バイオ計算のフライウェイトパターンの改善

背景



すぐに私は複雑さをおaびしますが、状況自体はこれを適用するのが難しく、それを解決する方法ですが、結果は美しく効果的です:)



それはOOP問題に関する 1つの問題を記述することから始まりました。 それから偶然、会話のおかげで、デザインパターンについて考え始めました。 また、「オブジェクトの完全なコピー」というトピックに関連して、彼はフライウェイトパターンになりました。 誰が知らない- 最初にオブジェクト指向のデザイントリックで彼について読んでください。 デザインパターン (ウィキではなく、オリジナルにあります)。



主なアイデアは次のとおりです。



Flyweightパターンは、非常に小さなオブジェクトを過度に高いコストなしで共有する方法を説明します。 各フィッティングオブジェクトには、内部状態と外部状態の2つの部分があります。 内部状態はフィクスチャに保存(共有)され、そのコンテキストに依存しない情報で構成されます。 外部状態はクライアントオブジェクトによって保存または計算され、メソッドが呼び出されると日和見主義者に渡されます。



挑戦する



これを具体的な例で改善する方法を見ていきます。 バイオ計算についてはほとんど説明しませんが、この例に基づいて例を作成します。 バイオ計算の本質を完全にエッチングして、スキームのみを残そうとします。



PS誰かがRNA /タンパク質を折り畳むというタスクでの生体計算の問題自体に興味がある場合-注文をして、別の記事を書きます。







そのため、RNAオブジェクト(RNA)があります。 彼は、より一般的なChainオブジェクトの相続人です(たとえば、DNA、タンパク質などもあります)。 各RNAは多くの分子(ヌクレオチド)(分子)で構成されています。 この場合、分子はシトシン、ウラシル、グアニン、アデニンの4種類(分子相続人)になります。 タイプに応じて、各分子は28〜33個の原子(原子)で構成されます。 各原子には3つの計算された角度があります。



パブリック クラス Angles

{

プライベート フロートファイ;

プライベート フロートシータ;

プライベート フロート d ;

}





3次元モデルを構築するために、たとえばaagaggucggcaccugacguなど、チェーンの一次構造に基づいて必要です。 つまり 一般にグラフである約1000個の相互接続された原子を作成します。 このグラフの作成には時間がかかります。



次に、バイオ計算を実行する必要があります。 このチェーンは、特定の3D構成を受け入れます。 各原子の座標と、他の原子に対するその角度が計算されます。 計算はスキームに従って実行されます。RNAの最後の「良好な」状態を取得し、それを「改善」しようとします。 1つのコーナーで1つの分子を1000回回転させようとするとします。 この1000から、最適なオプションが1つだけ選択されます-固定され(「良好な」状態になり)、計算が繰り返されます。



額の決定



実際、それは最初に決定され、Roseta @ homeプロジェクトのコードから継承されました。



ご覧のとおり、1000回ターンを試みるには、初期状態をコピーする必要があります。 つまり オブジェクトがあります



RNA BestRNA ;

RNA CurrentRNA ;




そしてする必要があります



CurrentRNA = BestRNA。 クローン ( ) ;

計算( CurrentRNA ) ;





問題はまさにこのクローン()です。 すべての原子のグラフを作成することは、非常にリソースを消費する手順であることを忘れないでください。

そして、構築しない場合、「良い」状態のすべての原子の角度を消去します。



クラシックフライ級



古典的なFlyweightでは、角のプロパティを原子から引き伸ばし、上のオブジェクトに配置することができます。 RNAオブジェクトに配置されるアレイの範囲。 この配列には何らかの方法でインデックスが付けられるため、インデックスによって目的のアトムに確実にアクセスできます。



次に、クローンを作成するときに、この配列のみをクローンする必要があり、原子のグラフは必要ありません。



しかし、これはすべて、OOPの原則に重大に違反しています。 実際、オブジェクトのプロパティは、計算の速度を上げるためにオブジェクトから選択されます。 モデルは本質を失います-角度は原子の特性ではなく、鎖全体の特性です。 軽々しく。 これは、オブジェクトプログラミングではなく、構造プログラミングの兆候です。



どうする



主なことは、私たちが実際に時間内に計算を行っていることを最初に認識することです。 つまり 原子の軌道を長期間維持する必要があります。 その後、オブジェクト画像が復元されます。



次に、最小限のコメントを含む多くのコードがありますが、これは明確な質問ではありません。



そのため、最初に行うことは、単一の角度を時間の配列に置き換えますが、現時点ではアクセスします。



パブリック クラス Angles

{

private float [ ] phi = new float [チェーン。 TimeHistory ] ;

private float [ ] theta = new float [チェーン。 TimeHistory ] ;

private float [ ] d = new float [チェーン。 TimeHistory ] ;

パブリック フロートファイ

{

get { return phi [チェーン。 時間 ] ; }

set { phi [チェーン。 時間 ] =値; }

}

公共 フロートシータ

{

get { return theta [チェーン。 時間 ] ; }

set { theta [チェーン。 時間 ] =値; }

}

公共 フロート D

{

get { return d [チェーン。 時間 ] ; }

set { d [チェーン。 時間 ] =値; }

}

public void SetInitial ( int argTime )

{

Phi = phi [ argTime ] ;

シータ=シータ[ argTime ] ;

D = d [ argTime ] ;

}

}




さらに時間は回路レベルで制御されます。 最上位のオブジェクト。 コードを渡して、徐々に説明します。 全体のアイデアは最後にのみ明確になります(ステートメントは数学のようです-最初に、なぜそれが必要であるかが明確ではなく、最後にのみ、なぜこれがすべてなのかが明確です)。



パブリック クラスチェーン

{

プライベート スタティックチェーンインスタンス;

パブリックチェーン( int MolCount )

{

インスタンス= this ;

}

///現在の時刻

private int time = 0 ;

public static int時間

{

get {インスタンスを返します。 時間 ; }

set {インスタンス。 時間 =値; }

}

public static int OldTime

{

得る

{

int oldTime =インスタンス。 時間 -1

if ( oldTime == - 1 )

{

oldTime = TimeHistory - 1 ;

}

oldTimeを返します。

}

}

///オブジェクトが保存するタイムステップの数

public static int TimeHistory = 5 ;



プライベート static int生成;

public static int Generation

{

get {世代を返す ; }

}

///時間を1ステップ進めて転送する

保護された void NextTime ( int argGeneration )

{

時間++

if ( Time > = TimeHistory )

{

時間= 0 ;

generation = argGeneration + 1 ;

CurrentMaxTimeID = 0 ;

}

}



public void CheckTime ( int argTimeID、 int argGeneration )

{

// 2世代以上前の世代は存在できません。現在の世代の場合、

//タイムスタンプは発行されたスタンプより長くすることはできません

//(これは現在の世代と前の世代の間でのみ可能です)

//そして、現在よりも大きな世代は存在できません

if ( argGeneration < ( generation - 1 ) ||

( argGeneration == generation && CurrentMaxTimeID < argTimeID ) ||

argGeneration >世代)

{

コンソール WriteLine ( "ErrorGenerationRNA" ) ;

コンソール ReadLine ( ) ;

}

if ( Time ! = argTimeID )

{

時間= argTimeID ;

}

}



private static int CurrentMaxTimeID = 0 ;

public static int GetNextTimeID ( int argCurrentTimeID、 int argGeneration )

{

int NextTime = -1 ;



if ( argGeneration == generation )

{

NextTime = argCurrentTimeID + 1 ;

}

if ( argGeneration == generation - 1 )

{

NextTime = 0 ;

}

if ( CurrentMaxTimeID < NextTime )

{

CurrentMaxTimeID = NextTime ;

}

NextTimeを返します。

}

}




チェーンはシングルトンパターンを実装しますが、非常に古典的ではありません。 最初に、特定のコンストラクターを介して最初のRNAオブジェクト(Chainの子孫)が作成されると、インスタンスへの参照が置き換えられます。 つまり これは、一般的な唯一のオブジェクトではありませんが、最後に作成されたオブジェクトです。 実際、オブジェクトへの参照は提供されていません。 現在の時刻のみ取得できます。 したがって、RNAからの「古いオブジェクト」はまだ存在する可能性がありますが、時間内には存在しません。



配列は、TimeHistoryの値によって作成されます。 これは、相対的な値であり、同時に持つことが重要な状態の軌跡のステップ数です。 この例では、一般に2つあります。BestとCurrentです。 ただし、最適化のために、わずかに5に増やします。



次に、行われていること。 RNAのプロパティ/メソッドへのアクセスを完全に制御して、時間内に機能させる必要があります。 「メディエーター」パターンを使用します(ただし、使用クラスはこれを理解しません-つまり、透過的です)



RNAクラスの名前をRNARealiseに変更します。 そして、実際のRNAの代わりに、「中間体」を作成します:



パブリック クラス RNA

{

プライベート RNARealise本体。



private int TimeID = 0 ;

private int GenerationID = 0 ;



パブリック分子[ ]分子

{

得る

{

体。 CheckTime ( TimeID、GenerationID ) ;

本体を返します。 分子 ;

}

}

パブリック RNA ( RNASeq argSeq )

{

body = 新しい RNARealise ( argSeq ) ;

}

パブリック RNAクローン( )

{

体。 CheckTime ( TimeID、GenerationID ) ;

RNA NewRNA = ( RNA ) これ 。 MemberwiseClone ( ) ;

NewRNA。 body = NewRNA。 体の クローン ( これは GenerationID ) ;

NewRNA。 TimeID =チェーン。 GetNextTimeID ( this.TimeID 、 this.GenerationID ) ;

NewRNA。 GenerationID =チェーン。 世代



NewRNAを返します。

}



public void Refold ( int argPosition )

{

体。 CheckTime ( TimeID、GenerationID ) ;

体。 リフォールド( argPosition ) ;

}

}




ここで重要なのは。 オブジェクトに識別子が追加されます-TimeIDタイムスタンプとGenerationIDオブジェクトの生成。 そして、プロパティへの各アクセスの前および毎回の呼び出しの前に、body.CheckTimeオブジェクト(TimeID、GenerationID)がその時間にあるかどうかをチェックします。



そして、パブリックRNAクローン()クローニングがどのようにエミュレートされるかを検討してください。



実際には、中間体のシェルのみがクローン化され、そこに時間と世代の新しいスタンプが割り当てられます。 内部には、NextTimeクローン時間(argGeneration)の翻訳もあります。 そして、分子クローニングのレベルでは、コーナーの初期化は以前の値になります



for ( int i = 1 ; i < FullAngles。Length ; i ++ )

{

FullAngles [ i ] 。 SetInitial (チェーン。OldTime ) ;

}





これは、完全なクローン作成よりもかなり少ないです。 そして、呼び出し元のコード全体はまったく変更されません-クローンが行われた場所で行われます。 計算には次のようなものがありました



public void BlockFolding ( )

{

RNA saveRNA = CurrentRNA。 クローン ( ) ;

locScore = NucFluctuation ( saveRNA ) ;

//見つかった最適な状態を保存します

保存( ) ;

}



パブリック ダブル NucFluctuation ( RNA argRNA )

{

for ( int j = 1 ; j < FragmentCount ; j ++ )

{

RNA locRNA = argRNA。 クローン ( ) ;

//回転させます

回転 AtomAngleRotate ( locRNA ) ;

//ターンの収益性の計算

RNAScore。 スコア ( locRNA ) ;

}

}





上からの時間を直接制御する人はいませんが、異なる時間の角度が混同されていないことがどのように起こるかを推測してみてください? (一般に、これを読者に「宿題」に任せます。それでも理解が難しい場合は、コメントから理解します)。



All Articles