# Simulations and explanation for the weight-decay and sp models
- date: 2021-0412
- author: Shin Asakawa
- Origin: Foygel and Dell (2000) Models of Impaired Lexical Access in Speech Production,
- Journal of Memory and Language 43, 182–216 (2000) doi:10.1006/jmla.2000.2716

```python
from IPython import display

print('Foygell and Dell (2000) Fig. 1. オリジナルは Dell (1997) 3 つの層の結合は双方向。\n上から意味層, 語彙層, 音素層。最上層の意味層で 暗く塗りつぶされているニューロンは `cat`, `dog`, `rat` で共有されていることを示す。')

display.Image(url='https://raw.githubusercontent.com/project-ccap/project-ccap.github.io/master/figures/2000Foygel_Dell_fig1.png', width=480)

```

```python
print('必要となるライブラリの読み込み...', end=" ")
import numpy as np
import sys
import time

from numpy.random import Generator, PCG64
rng = Generator(PCG64())
#rng.standard_normal()

# 表示精度桁数の設定
#np.set_printoptions(suppress=False, formatter={'float': '{:7.4f}'.format})
np.set_printoptions(suppress=False, formatter={'float': '{:6.3f}'.format})

import matplotlib.pyplot as plt
%matplotlib inline

#事前に japanize_matplotlib のインストールが必要です。次行，行頭の #を削除してく，このセルを実行してください
!pip install japanize_matplotlib
import japanize_matplotlib
print('done')

rng = Generator(PCG64())  # 乱数の定義
```

```python
print('#Dell モデルの再現実験, 原著論文 Foygell and Dell (2000)を参照のこと')
FoygellDell_tab1={
    #Table 1 のデータ
    'LH':[0.69,0.03,0.07,0.15,0.01,0.02],
    'IG':[0.69,0.09,0.05,0.02,0.03,0.01]
}

FoygellDell_tab2_and_4={
    #Table 2 に記載のデータ。小数点以下４桁目が必要なのか疑問。理由は PNT の検査図版は 175 枚しかない。
    #どんなに努力しても一回の検査で得られるデータは 175 しかないのだから，小数点以下 2 桁で十分ではないかなー
    #各行のデータ， 最初の 6 列は，それぞれののデータ，
    #2行目最後の 4 列は weight, decay パラメータの推定値を表す. Foygell and Dell (2000)table 4 より引用
    #3行目は Tab.4 の sp モデル
    'W.B.': [[0.9400,0.0200,0.0100,0.0100,0.0100,0.0000,0.0200,0.5600, 0.0352, 0.0274],
             [0.9416,0.0309,0.0063,0.0137,0.0054,0.0021,0.0255,0.5781,0.0054,2.282],
             [0.9419,0.0282,0.0076,0.0172,0.0047,0.0004,0.0352,0.0274,0.0051, 2.194]],
    'T.T.': [[0.9300,0.0100,0.0100,0.0000,0.0200,0.0000,0.0200,0.5600, 0.0340, 0.0866],
             [0.9247,0.0496,0.0063,0.0055,0.0131,0.0008,0.0542,0.6758,0.0168,7.657],
            [0.9689,0.0077,0.0109,0.0000,0.0125,0.0000,0.0340,0.0866,0.0162,1.194]],
    'J.Fr.':[[0.9200,0.0100,0.0100,0.0200,0.0200,0.0000,0.0200,0.5600, 0.0316, 0.0305],
             [0.9061,0.0536,0.0111,0.0160,0.0116,0.0016,0.0470,0.6602,0.0191,7.782],
             [0.9332,0.0366,0.0074,0.0140,0.0070,0.0018,0.0316,0.0305,0.0135,8.566]],
    'V.C.':[[0.8700,0.0200,0.0100,0.0300,0.0100,0.0000,0.0200,0.5700, 0.0407, 0.0229],
            [0.9145,0.0390,0.0112,0.0265,0.0056,0.0032,0.0213,0.5703,0.0199,3.267],
            [0.9317,0.0246,0.0079,0.0305,0.0053,0.0000,0.0407,0.0229,0.0253,1.694]],
    'L.B.':[[0.8200,0.0400,0.0200,0.0900,0.0100,0.0100,0.0070,0.5000, 0.0274, 0.0221],
            [0.8248,0.0528,0.0300,0.0708,0.0066,0.0150,0.0087,0.5156,0.0107,2.641],
            [0.8213,0.0534,0.0273,0.0828,0.0075,0.0077,0.0274,0.0221,0.0070,1.306]],
    'J.B.':[[0.7600,0.0600,0.0100,0.0500,0.0200,0.0100,0.0065,0.5000, 0.0264, 0.0246],
            [0.8288,0.0798,0.0266,0.0374,0.0183,0.0091,0.0523,0.6953,0.0305,4.458],
            [0.8419,0.0564,0.0259,0.0561,0.0084,0.0113,0.0264,0.0246,0.0345,6.088]],
    'J.L.':[[0.7600,0.0300,0.0100,0.0600,0.0300,0.0100,0.0250,0.6000, 0.0255, 0.0221],
            [0.8288,0.0798,0.0266,0.0374,0.0183,0.0091,0.0523,0.6953,0.0368,11.966],
            [0.7921,0.0601,0.0375,0.0867,0.0107,0.0129,0.0255,0.0221,0.0251,14.040]],
    'G.S.': [[0.7000,0.0200,0.0600,0.1500,0.0100,0.0200,0.0057,0.5000, 0.0246, 0.0191],
             [0.6894,0.0673,0.0615,0.1404,0.0087,0.0327,0.0070,0.5156,0.0208,6.865],
             [0.7182,0.0555,0.0505,0.1511,0.0080,0.0167,0.0246,0.0191,0.0168,4.570]],
    'L.H.':[[0.6900,0.0300,0.0700,0.1500,0.0100,0.0200,0.0057,0.5000,0.0237,0.0178],
            [0.6767,0.0700,0.0671,0.1466,0.0088,0.0308,0.0065,0.5117,0.0179,4.773],
            [0.6712,0.0573,0.0661,0.1768,0.0085,0.0201,0.0237,0.0178,0.0175,3.166]],
    'J.G.':[[0.5500,0.0600,0.0800,0.1800,0.0400,0.0300,0.0450,0.7000,0.0191,0.0172],
            [0.5543,0.1121,0.1091,0.1542,0.0246,0.0457,0.0514,0.7227,0.0281,8.988],
            [0.5667,0.0746,0.0882,0.2170,0.0123,0.0412,0.0191,0.0172,0.0217,13.273]],
    'E.G.':[[0.9300,0.0300,0.0000,0.0100,0.0200,0.0000,0.1000,0.6000,0.0316,0.0305],
            [0.9222,0.0498,0.0017,0.0062,0.0199,0.0002,0.0698,0.7148,0.0089,2.129],
            [0.9332,0.0336,0.0074,0.0140,0.0070,0.0018,0.0316,0.0305,0.0070,6.245]],
    'B.Me.':[[0.8400,0.0300,0.0100,0.0000,0.0500,0.0100,0.1000,0.8200,0.0165,0.0866],
             [0.8424,0.0925,0.0147,0.0216,0.0258,0.0030,0.0750,0.7617,0.0290,18.265],
             [0.8468,0.0313,0.0938,0.0000,0.0177,0.0104,0.0165,0.0866,0.0368,23.438]],
    'B.Mi.':[[0.8300,0.0500,0.0100,0.0100,0.0200,0.0100,0.0550,0.7000,0.0255,0.0328],
             [0.8712,0.0620,0.0202,0.0280,0.0120,0.0066,0.0407,0.6484,0.0198,4.914],
            [0.8973,0.0538,0.0181,0.0128,0.0078,0.0102,0.0255,0.0328,0.0282,5.012]],
    'J.A.':[[0.7800,0.0400,0.0000,0.0200,0.0300,0.0100,0.0580,0.7000,0.0246,0.0294],
            [0.8455,0.0824,0.0191,0.0259,0.0234,0.0037,0.0626,0.7227,0.0331,10.487],
            [0.8627,0.0637,0.0263,0.0236,0.0093,0.0144,0.0246,0.0294,0.0378,15.927]],
    'A.F.':[[0.7500,0.0200,0.0300,0.0700,0.0600,0.0400,0.1000,0.8500,0.0205,0.0229],
            [0.7318,0.1004,0.0565,0.0679,0.0258,0.0176,0.0573,0.7246,0.0391,26.456],
            [0.7113,0.0841,0.0644,0.0935,0.0123,0.0344,0.0205,0.0229,0.0401,45.699]],
    'N.C.':[[0.7500,0.0300,0.0700,0.0800,0.0100,0.0000,0.1000,0.8500,0.0237,0.0221],
            [0.7683,0.0621,0.0488,0.0929,0.0079,0.0200,0.0078,0.5156,0.0199,8.503],
            [0.7693,0.0638,0.0479,0.0907,0.0082,0.0201,0.0237,0.0221,0.0205,8.810]],
    'I.G.':[[0.6900,0.0900,0.0500,0.0200,0.0300,0.0100,0.1000,0.8600,0.0198,0.0340],
            [0.7594,0.1116,0.0475,0.0457,0.0255,0.0103,0.0661,0.7500,0.0315,4.534],
            [0.7948,0.0863,0.0544,0.0131,0.0138,0.0376,0.0198,0.0340,0.0449,10.018]],
    'H.B.':[[0.6100,0.0600,0.1300,0.1800,0.0200,0.0100,0.0500,0.7130,0.0191,0.0172],
            [0.5526,0.1002,0.1120,0.1697,0.0170,0.0485,0.0400,0.6816,0.0337,9.922],
            [0.5667,0.0746,0.0882,0.2170,0.0123,0.0412,0.0191,0.0172,0.0322,10.628]],
    'J.F.':[[0.5600,0.1400,0.0100,0.0200,0.1100,0.0100,0.1000,0.8600,0.0107,0.0365],
            [0.7129,0.1359,0.0452,0.0587,0.0391,0.0082,0.0982,0.8594,0.0721,37.590],
            [0.5237,0.1369,0.1662,0.0136,0.0178,0.1418,0.0107,0.0365,0.0928,131.684]],
    'G.L.':[[0.2800,0.0400,0.2100,0.3000,0.0300,0.0900,0.0790,0.8500,0.0093,0.0154],
            [0.2500,0.0985,0.2036,0.3121,0.0282,0.1076,0.0806,0.8594,0.0284,7.351],
            [0.2716,0.1020,0.1565,0.3230,0.0137,0.1332,0.0093,0.0154,0.0397,15.974]],
    'W.R.':[[0.0800,0.0600,0.1500,0.2800,0.0500,0.3300,0.1000,0.9400,0.0010,0.0178],
            [0.1598,0.0859,0.2027,0.4039,0.0242,0.1235,0.0965,0.9336,0.1068,82.627],
            [0.1343,0.1179,0.2319,0.2594,0.0132,0.2433,0.0010,0.0178,0.0610,37.527]]}

def draw_dell_graph(A, B,
                    title=None,
                    width_inches=7, height_inches=4,
                    alabel='健常統制群データ(Dell,1997)', acolor='red',
                    blabel='beta 調整後のシミュレーション結果', bcolor='green',
                    fontsize=16):
    """Dell モデルのグラフ描画
    A と B 比較対象の２つのモデル出力について，６種類の反応カテゴリの
    棒グラフを描画

    引数:
    A: np.array((6))
    B: np.array((6))
    出力値:
        なし
    """
    fig = plt.figure(figsize=(width_inches,height_inches))
    ax = fig.add_axes([0,0,1,1])
    X = np.arange(B.shape[0])

    ax.bar(X-0.2, A, color=acolor, width=0.4, label=alabel)
    ax.bar(X+0.2, B, color=bcolor, width=0.4, label=blabel)
    plt.legend(fontsize=fontsize)
    if isinstance(title, str):
        plt.title(title,fontsize=fontsize)
    # https://www.javaer101.com/ja/article/5091810.html
    ax.set_xticks(ax.get_xticks().tolist())
    ax.set_ylim(bottom=0, top=1.0)
    ax.set_xticklabels(['', '正解', '意味エラー','形態エラー','混合エラー','無関連エラー', '非単語エラー', ''],fontsize=int(fontsize*0.8))

    plt.show()

#plt.title('Dell(1997) Tab. 4 より作成:')
# Dell(1997)による健常者のデータ
Dells_controls = np.array([0.9690, 0.0120, 0.0010, 0.0090, 0.0030, 0.0000])

# Dell(1997)による WD モデルのデータ
Dells_WD = np.array([0.9660, 0.0210, 0.0000, 0.0120, 0.0000, 0.0010])
draw_dell_graph(Dells_controls, Dells_WD,
                title='Dell model の結果図示',
                alabel="Dell(1997)健常者のデータ",
                blabel="Dell(1997) WD モデル")

# Foygel & Dell(2000)による SP モデルのデータ, Tab. 3 Foygel and Dell (2000)
Dells_SP = np.array([0.9722, 0.0126, 0.0011, 0.0138, 0.0002, 0.0001])
draw_dell_graph(Dells_controls, Dells_SP,
                alabel="Dell(1997)健常者のデータ",
                blabel="Dell(1997) SP モデル")

```

```python
def draw_dell_graph3(A, B, C, name='noname',
                     title = None,
                     acolor='green', bcolor='red', ccolor='blue',
                     alabel='健常統制群データ(Dell,1997)',
                     blabel='Weight decay',
                     clabel='s-p model',
                     width_inches=4, height_inches=3,
                     fontsize=12):
    """Dell モデルのグラフ描画
    A, B, C 比較対象の 3 つのモデル出力について，６種類の反応カテゴリの棒グラフを描画

    引数:
    A: np.array((6))
    B: np.array((6))
    C: np.array((6))
    出力値:
        なし
    """
    fig = plt.figure(figsize=(width_inches,height_inches))
    ax = fig.add_axes([0,0,1,1])  #  [left, bottom, width, height] of the new axes
    A, B, C = np.array(A[:6]), np.array(B[:6]), np.array(C[:6])
    X = np.arange(B.shape[0])

    ax.bar(X-0.2, A, color=acolor, width=0.2, label=alabel)
    ax.bar(X    , B, color=bcolor, width=0.2, label=blabel)
    ax.bar(X+0.2, C, color=ccolor, width=0.2, label=clabel)
    plt.legend(fontsize=fontsize)
    if isinstance(title, str):
        plt.ttile(title)

    # https://www.javaer101.com/ja/article/5091810.html
    ax.set_xticks(ax.get_xticks().tolist())
    ax.set_ylim(bottom=0, top=1.0)
    ax.set_title('Patient {0}'.format(name))
    ax.set_xticklabels(['', '正解', '意味エラー','形態エラー','混合エラー','無関連エラー', '非単語エラー', ''],fontsize=int(fontsize*0.8))
    plt.show()

```

```python
np.set_printoptions(suppress=False, formatter={'float': '{:4.2f}'.format})

print('#Dell のデータ, 実データ, wd model, sp model, Foygell and Dell (2000) Table 2 and 4 を参照のこと')
for patient in FoygellDell_tab2_and_4:
    data_n = 0
    data_n_wd = 1
    data_n_sp = 2
    data = np.array(FoygellDell_tab2_and_4[patient][data_n][:6])
    wd_data = np.array(FoygellDell_tab2_and_4[patient][data_n_wd][:6])
    sp_data = np.array(FoygellDell_tab2_and_4[patient][data_n_sp][:6])
    print('{0:5s} {1} {2} {3}'.format(patient, data, wd_data, sp_data))
    draw_dell_graph3(data, wd_data, sp_data, name=patient,
                     alabel=patient, blabel='WD モデル推定値', clabel='SP モデル推定値',
                     width_inches=6, height_inches=3,
                     fontsize=12,
                     ccolor='black', acolor='gray', bcolor='pink')

```

```python
print('#ニューラルネットワーク業界で頻用される出力関数の一行野郎 one liner 定義...', end='')
sigmoid = lambda x: 1/(1+np.exp(-x)) # ロジスティックシグモイド関数
d_sigmoid = lambda x: x * (1-x)

ReLU = lambda x: np.maximum(0, x)
d_ReLU = lambda x:[1 if x_ > 0 else 0 for x_ in x]

tanh = lambda x: np.tanh(x)
d_tanh = lambda x: 1 - x**2
print('done')

print('#ソフトマックス関数の定義...', end='')
def softmax(x, beta=1.):
    """softmax 関数の定義
    引数
    x: np.array
        softmax を行う変数群，ベクトルを仮定
    beta: float
        逆温度，この値の逆数が温度

    戻り値
    x.shape[0] 次元の多項確率密度
    """
    if not isinstance(x, np.ndarray):
        return None

    if x.ndim == 1:
        return np.exp(x * beta)/np.exp(x * beta).sum()
    else:
        return np.array([np.exp(x_ * beta)/np.exp(x_ * beta).sum() for x_ in x])

print('done')

print('#交差エントロピー誤差関数との定義...', end='')
def CE_loss(teach, pred):
    """
    交差エントロピー誤差の計算
    - 引数
    teach: np.array
        教師信号ベクトル
    pred: np.array
        予測値ベクトル

    - 戻り値
    loss: float
        損失関数の値
    """
    epsilon = np.finfo(np.float).eps
    #epsilon = 0.001
    #loss = - (teach * np.log(pred) + (1-teach) * np.log(1-pred)).sum()
    loss = 0.
    for t, p in zip(teach, pred):
        if t > epsilon and p > epsilon:
            loss += t * np.log(p)

    return -1. * loss

print('done')

```

    #ニューラルネットワーク業界で頻用される出力関数の一行野郎 one liner 定義...done
    #ソフトマックス関数の定義...done
    #交差エントロピー誤差関数との定義...done

# メモ

合成関数の微分則から，損失関数をパラメータ $\mathbf{\theta}$ の各成分 $\beta,\theta_d,\theta_s,\theta_p$ で微分することで勾配降下法による学習を定義できる。
Dell の動作方程式を若干書き換える

$$
x_{t+1} = (1-d)x_t + \theta_{s,p} \sum_i w_i x_i +\mathcal{N}(0,a_1^2+a_2^2)\hspace{1em}\text{(Dell's original)}
$$

$$
y_{i,t+1} = a\left((1-\theta_d)x_{i,t} + \theta_{s,p} \sum_j w_{j,t-1} x_{j,t}+b_i\right) \hspace{1em}\text{(modified)}
$$

ここで， $a$ は活性化関数である。$a=\mathbb{1}$ のとき Dell モデルに一致する。
$y$ は出力値， $x$ は内部状態とする。元来，Dell モデルは RNN とみなせるで，内部状態と外部出力とを区別することが妥当であると思われる。

\begin{equation}
\begin{split}
\frac{\partial l}{\partial \theta}&=\frac{\partial l}{\partial p}\frac{\partial p}{\partial{y}}\frac{\partial y}{\partial \theta}\\
&\sum_i\frac{t_i-p_i}{p_i\left(1-p_i\right)}\sum_j p_j\left(\delta_{ij}-p_j\right)\frac{\partial y_j}{\partial\theta}\\
\end{split}
\end{equation}
ここで $\delta_{ij}$ は Dirac の delta であるとする。

$a$ が Dell の 式(1) であるとすると $\partial l/\partial\theta$ は次式のようになる:
\begin{equation}
\begin{split}
\frac{\partial l}{\partial\theta}&=\sum_i\frac{t_i-p_i}{p_i\left(1-p_i\right)}\sum_j p_i\left(\delta_{ij}-p_j\right) \frac{d}{da_j}\frac{\partial a_j}{\partial\theta}\hspace{5cm}\text{ここで， $\frac{d}{da_j}\frac{\partial a_j}{\partial\theta}=c_j$ とおくと}\\
&=\sum_i\frac{t_i-p_i}{p_i\left(1-p_i\right)}p_i\left(c_i-\sum_jp_jc_j\right)\\
&=\sum_i\frac{t_i-p_i}{1-p_i}\left(c_i-\bar{c}\right),\\
\end{split}
\end{equation}
ただし，$\mu_{x_{t-1}}$ は下位層のニューロンの出力に確率 $p_i$ を掛けて総和したもの，すなわち平均である。

以下では，
- $t_i$: `teacher`
- $p_i$: `pred`
- $t_i-p_i$: `delta`

```python
class Dell2021():
    """Optimization for the Foygell and Dell model's parameter vector.
    ソフトマックス関数により多項分布の確率密度に変換した値から
    損失関数を算出し，その損失間数値に基づいて勾配降下法に従って各パラメータ最適化を行う
    ただし beta は逆温度である。1/beta が実際の温度

    $\beta$ に加えて，$\theta=(w,d,s,p)$ についても $\partial l/\partial\theta$ を用いて
    更新する
    """

#    def __init__(self, w=0.1, d=0.5, s=0.0698, p=0.1,
    def __init__(self, d=0.5, param=None, # w=0.1, d=0.5, s=1., p=1.,
                 beta_init=1., iter_max=10**5, lr=0.5,
                 time_n = 16,
                 teacher=None,
                 interval=None, verbose=True, nonstop=False,
                 IFG_beta=15.,  # 頭頂葉の関与を表す
                 f=tanh, Weights='R', model='WD', data=None):
        """
        - 引数
        beta_init: float
            ソフトマックス関数を計算する際に用いる温度パラメータの初期値
        iter_max: int
            繰り返し回数の上限値。Dell のオリジナルモデルは iter_max = 16 に固定されていた
        lr: float
            学習係数
        interval: int
            途中結果を出力する際の間隔。
            デフォルトでは iter_max の 1/10 毎に途中結果を出力する
        verbose: bool
            True なら途中結果の冗長な出力を印字する
            デフォルト: True
        nonstop: bool
            途中で学習を打ち切るフラグ
            デフォルト: False
        f: function
            出力関数
        W: str
            'R': 相関係数行列を用いる
            'softmax': 相関係数をソフトマックスに変換して用いる
            else: 相関係数行列のソフトマックス変換を用いる
        """
        self.sem_jolt = 10 # 元論文 (Foygell and Dell, 2000) page 185 footnote
        self.lex_jolt = 100
        self.jolt_t = 7  # カウントが 0 ベースなので t=7 (8-1) が 8 回目

        if param == None:
            self.param = {'WD':{'Weight':0.1,  # for normal
                                'Decay':0.5    # for normal
                               },
                          'SP':{'Decay': 0.6,
                                'S_Weight':0.0698,
                                'P_Weight':0.1000
                               }}
        else:
            self.param = param

        if model=='WD':
            self.s, self.p = self.param['WD']['Weight'], self.param['WD']['Weight']
        else:
            self.s, self.p = self.param['SP']['S_Weight'], self.param['SP']['P_Weight']

        # 音素の定義
        self.phonemes = {'onset':['f', 'r', 'd', 'k', 'm'],
                         'vowel':['ae', 'o'],
                         'coda':['t','g']}
        self.Onset, self.Vowel, self.Coda = slice(0,5), slice(5,7), slice(7,9)

        self.teacher = teacher
        self.data = None
        self.nonstop = nonstop
        self.beta = beta_init
        self.iter_max = iter_max
        self.lr = lr
        if interval == None:
            # interval が定義されていなければ設定する
            self.interval = iter_max * 10**-1
        else:
            self.interval = interval
        if verbose == None:
            self.verbose = False
        else:
            self.verbose = True
        #self.epsilon = 10 ** -5
        self.epsilon = np.finfo(np.float).eps

        self.max_t = 16  # Dell の 1 試行あたりの繰り返し数 相互活性化モデルでの繰り返し更新回数

        self.Weights = Weights
        if self.Weights == 'R':              #相関係数
            _, self.Ws, _ = self.init_Ws()
            _, self.Wp, _ = self.init_Wp()
        elif self.Weights == 'softmax':      #ソフトマックス
            _, _, self.Ws = self.init_Ws()
            _, _, self.Wp = self.init_Wp()
        else:                                #オリジナル
            self.Ws, _, _ = self.init_Ws()
            self.Wp, _, _ = self.init_Wp()
        self.lexicon_candidates = list(self.phonology.keys())

        # nubmer of neurons of each layer
        #self.n_sem, self.n_lex, self.n_pho, self.n_phon = 6, 6, 6, 9
        self.n_sem = self.Ws.shape[1]
        self.n_lex = self.Ws.shape[0]
        if self.n_lex != self.Wp.shape[0]:  #整合性チェックのため
            assert('self.n_lex != self.Wp.shape[0]:')
        self.n_pho = self.Wp.shape[1]

        self.sem_slice = slice(0,                    self.n_sem)
        self.lex_slice = slice(self.n_sem,           self.n_sem+self.n_lex)
        self.pho_slice = slice(self.n_sem+self.n_lex,self.n_sem+self.n_lex+self.n_pho)

        self.time_n = time_n
        self.IFG_beta = IFG_beta
        self.decay = d
        #self.a1, self.a2 = 0.01, 0.16
        self.a1, self.a2 = 0., 0.
        self.f = f

        self.W = self.make_W()
        if data == None:
            self.teacher = np.array([0.97, 0.01, 0.00, 0.01, 0.00, 0.00])
            self.one_trial(verbose=verbose)
        else:
            init_neurons()
            self.data = data

        return

    def init_Ws(self):
        Ws = np.array([[1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0],             # cat 正解
                       [0,0,0,0,0,0,0,1,1,1, 1,1,1,1,1,1,1,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0],             # dog 意味エラー
                       [0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1,1,1,
                        1,1,1,1,1,1,1,0,0,0, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0],             # mat 形態エラー
                       [0,0,0,0,0,0,0,1,1,1, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,1,1,1, 1,1,1,1,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0],             # rat 混合エラー
                       [0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0,1,1,1,1,1,1,
                        1,1,1,1,0,0,0,0,0,0, 0,0,0,0],             # fog 無関連エラー
                       [0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,1,1,1,1,1,1, 1,1,1,1]              # lat 非単語
                      ], dtype=np.float)
        Ws *= self.s
        R_ws = np.corrcoef(Ws)
        return Ws, R_ws, softmax(R_ws,beta=1)

    def init_Wp(self):
        # 語彙層と音韻層とを結ぶ結合係数行列の定義
        #                                     f.   r.   d.   k.   m.   ae.  o.   t.   g
        self.phonology = {'cat': np.array([ 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0]),  # correct
                          'dog': np.array([ 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0]),  # semantic error
                          'mat': np.array([ 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0]),  # formal error
                          'rat': np.array([ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0]),  # mixed error
                          'fog': np.array([ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0]),  # unrelated error
                          'lat': np.array([ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0])}  # nonword
        # phonology は BOW 形式なんですなー
        Wp = np.zeros((len(self.phonology), len(self.phonology['cat'])), dtype=np.float)
        for i, x in enumerate(self.phonology.values()):
            Wp[i] = np.copy(x)

        Wp *= self.p
        R_wp = np.corrcoef(np.array([self.phonology[item] for item in self.phonology]))
        return Wp, R_wp, softmax(R_wp, beta=1)

    def make_W(self):
        """大きな遷移行列の作成
            意味，語彙，音韻の3層を一列にならべてベクトル表現した場合

        戻り値:
        - W: np.array
            単一結合係数行列
        """
        if self.Weights == 'R':              #相関係数
            _, self.Ws, _ = self.init_Ws()
            _, self.Wp, _ = self.init_Wp()
        elif self.Weights == 'softmax':      #ソフトマックス
            _, _, self.Ws = self.init_Ws()
            _, _, self.Wp = self.init_Wp()
        else:                                #オリジナル
            self.Ws, _, _ = self.init_Ws()
            self.Wp, _, _ = self.init_Wp()

        nL, nS, nP = self.n_lex, self.n_sem, self.n_pho

        Isem, Ilex, Ipho = np.eye(nS), np.eye(nL), np.eye(nP)
        Dsem, Dlex, Dpho = np.eye(nS) * self.decay, np.eye(nL) * self.decay, np.eye(nP) * self.decay

        Z_sl = np.zeros((nS, nP))
        c1 = np.concatenate((Isem-Dsem, self.Ws.T, Z_sl), axis=1)
        c2 = np.concatenate((self.Ws, Ilex-Dlex, self.Wp), axis=1)
        c3 = np.concatenate((Z_sl.T, self.Wp.T, Ipho-Dpho), axis=1)
        W = np.concatenate((c1,c2,c3))
        return W

    def init_neurons(self):
        """各層のニューロンを初期化"""
        Sem = np.zeros([self.n_sem,], dtype=np.float)  # 意味層
        Lex = np.zeros([self.n_lex,], dtype=np.float)  # 語彙層
        Pho = np.zeros([self.n_pho,], dtype=np.float)  # 音韻層

        # Sem[0] すなわち cat 画像だけ 1 に設定する。すなわちネコ画像が入力されたことを意味
        #Sem[0] = 1.
        Sem[:10] = np.copy(np.ones(10)) * self.sem_jolt

        ret = np.concatenate((Sem,Lex,Pho))  # 一行のベクトルになるように改変
        return ret

    def split(self, X=None):
        if not isinstance(X, np.ndarray):
            X = np.copy(self.y)
        #s,l,p = np.copy(self.y[self.sem_slice]), np.copy(self.y[self.lex_slice]), np.copy(self.y[self.pho_slice])
        s,l,p = np.copy(X[self.sem_slice]), np.copy(X[self.lex_slice]), np.copy(X[self.pho_slice])

        return s, l, p

    def merge(self, s, l, p):
        ret = np.concatenate((s,l,p))
        return ret

    #def update(self, Sem, Lex, Pho, f=None):
    def update(self, f=None):
        """Dell の 3 層相互活性化モデルに従って各層のニューロンの活性値を更新

        引数:
        - f: function
            出力関数。指定しなければ変換せずに値を出力
            Dell らのモデルは恒等写像。そのため各ニューロンの活性値が指数関数的増加，減衰を示す。
            それを回避するためには何らかの正規化，正則化が必要になると考えられる。
            Sigmoid, tanh, ReLu,

        戻り値:
        #- Sem_next: np.array
        #    次刻の意味層ベクトル
        #- Lex_next: np.array
        #    次刻の語彙層ベクトル
        #- Pho_next: np.array
        #    次刻の音韻層ベクトル
        """

        self.Sem, self.Lex, self.Pho = self.split(self.y)
        Sem_next = self.Sem * (1. - self.decay) + self.s * np.matmul(self.Ws.T, self.Lex)
        Lex_next = self.Lex * (1. - self.decay) + self.s * np.matmul(self.Ws, self.Sem) + self.p * np.matmul(self.Wp, self.Pho)
        Pho_next = self.Pho * (1. - self.decay) + self.p * np.matmul(self.Wp.T, self.Lex)

        # ノイズの付加
        for x in [Sem_next, Lex_next, Pho_next]:
            for i, xx in enumerate(x):
                xx += rng.standard_normal() * (self.a1 + self.a2 * xx)
                #x[i] = max(0, xx)  # ReLU

        # 非線形変換 or not
        if f == None:
            self.Sem, self.Lex, self.Pho = Sem_next, Lex_next, Pho_next
        else:
            self.Sem, self.Lex, self.Pho = f(Sem_next), f(Lex_next), f(Pho_next)

        #return self.Sem, self.Lex, self.Pho
        return self.merge(self.Sem, self.Lex, self.Pho)

    def grad(self, Teach, X):
        # 予測値
        Prob = softmax(X, beta=self.beta)

        # 教師信号と出力値との差分ベクトル
        Delta = Teach - Prob

        # 誤差の微分
        g_Delta = Delta / (Prob * (1-Prob))

        return Prob, Delta, g_Delta

    def IFG_process(self):
        """IFg: Inferior Frontal gyrus から／へ の相互作用を実現する
        """
        ##self.Sem = softmax(self.Sem, beta=beta_fixed)
        #Lex = self.y[self.n_sem:self.n_sem+self.n_lex]
        #Lex = softmax(Lex, beta=self.IFG_beta)

        #Pho = self.y[self.n_sem+self.n_lex:]
        #Pho = softmax(Pho, beta=self.IFG_beta)

        #self.y[self.n_sem:self.n_sem+self.n_lex] = np.copy(Lex)
        #self.y[self.n_sem+self.n_lex:] = np.copy(Pho)
        #self.Lex = softmax(self.Lex, beta=self.IFG_beta)
        #self.Pho = softmax(self.Pho, beta=self.IFG_beta)

        Sem, Lex, Pho = self.split()
        self.y[self.lex_slice] = np.copy(softmax(Lex))

    def one_trial(self, verbose=False):
        """Dell モデルの一試行を実施
        言い換えれば一枚のネコ画像を提示して反応を収集

        argments:
        max_t: int
            相互活性化モデルでの最終時刻，相互活性を繰り返す回数の上限値
            デフォルトでは 16 回。
            原著論文 (Foygell and Dell, 2000) では n と記載されている
        beta_fixed: float
            前頭葉 へ／から の相互作用の影響を制御するパラメータ
            実際にはソフトマックス関数の温度パラメータ

        returns:
        self.y: np.array

        sefl.seriesY: np.array((self.y, max_t))
            各層の履歴
        """
        time_n = self.time_n
        self.y = self.init_neurons()       #各層のニューロンの初期値の設定

        # BPTT を用いて, w, d, s, p の各パラメータを更新するため各ニューロンの履歴を保存領域の確保
        self.seriesY = np.zeros((time_n, len(self.y)), dtype=np.float)

        for t in range(self.max_t):
            if t == self.jolt_t:
                s,l,p = self.split()
                l[np.argmax(l)] = self.lex_jolt
                self.y[self.lex_slice] = np.copy(l)

            if verbose:
                if t == 0:
                    print('         {0}'.format(self.lexicon_candidates))

                lex = self.convert_onset_vowel_coda_2_lexicon(self.y)
                pred = softmax(lex, beta=self.beta)
                loss = CE_loss(self.teacher, pred)
                print('t={0:02d} lex:{1}'.format(t+1, self.y[self.lex_slice]), end=' ')
                print('pred:{0} loss:{1:.3f} beta:{2:.3f}'.format(pred, loss, self.beta))

            self.seriesY[t] = np.copy(self.y)
            self.y = np.dot(self.W, self.y)   #各層のニューロンの値を更新

        return self.y, self.seriesY

    #def convert_onset_vowel_coda_2_lexicon_invert(self, X, verbose=False):
    def convert_onset_vowel_coda_2_lexicon(self, X, verbose=False):
        """phonlogy 表現からもっともらしい語彙表現を検索し，その当てはまり度合いから
        語彙表象の値を返すことを考える。
        今一つのアイデアとしては，
        phonolgy から lexicon への逆変換を考えて，そこでの値をソフトマックスする。
        1. phonolgy 層を onset, vowel, coda の 3 部分に分解
        2. 分解した各音で softmax を実施し確率密度に変換
        3. 3 つの部分を合成して phoneme 表現とする
        4. phoneme 表現と phonology の 6 単語間の交差エントロピー誤差を計算する
        5. 6 単語との交差エントロピー誤差の値から，最小値を引き， (最大値-最小値) で割ることで 0-1 に変換
        6. マイナス 1 を掛けて 上限反転させて +1 することで， 0 から 1 までの値にする
        """
        val = np.finfo(np.float).max     #np で定義済の安全な最大値を代入
        #val = 0.
        name = 'NG'                      #len(words) + 1 語彙名のダミー
        s, lexeme, phoneme = self.split(X)
        lex = np.dot(self.Wp, phoneme)
        #lex = sigmoid(lex)
        return lex

        for i, word in enumerate(self.phonology):
            loss = CE_loss(phonology[word], phoneme)
            lex[i] = loss
            if loss < val:
                name = word
                val = loss
        lex_diff = lex.max() - lex.min()
        lex = - ((lex - lex.min()) / lex_diff) + 1.
        return lex

    def convert_onset_vowel_coda_2_lexicon_ok(self, X, verbose=False):
        """phonlogy 表現からもっともらしい語彙表現を検索し，その当てはまり度合いから
        語彙表象の値を返すことを考える。
        1. phonolgy 層を onset, vowel, coda の 3 部分に分解
        2. 分解した各音で softmax を実施し確率密度に変換
        3. 3 つの部分を合成して phoneme 表現とする
        4. phoneme 表現と phonology の 6 単語間の交差エントロピー誤差を計算する
        5. 6 単語との交差エントロピー誤差の値から，最小値を引き， (最大値-最小値) で割ることで 0-1 に変換
        6. マイナス 1 を掛けて 上限反転させて +1 することで， 0 から 1 までの値にする
        """
        val = np.finfo(np.float).max     #np で定義済の安全な最大値を代入
        #val = 0.
        name = 'NG'                      #len(words) + 1 語彙名のダミー
        s, lexeme, phoneme = self.split(X)
        onset, vowel, coda = softmax(phoneme[:5]), softmax(phoneme[5:7]), softmax(phoneme[7:])
        phoneme = np.concatenate((onset,vowel,coda))
        lex = np.zeros_like(lexeme)

        for i, word in enumerate(self.phonology):
            loss = CE_loss(phonology[word], phoneme)
            lex[i] = loss
            if loss < val:
                name = word
                val = loss
        lex_diff = lex.max() - lex.min()
        #lex = - (lex - lex.min() / lex_diff) + 1.
        lex = - ((lex - lex.min()) / lex_diff) + 1.
        return lex

    def convert_onset_vowel_coda_2_lexicon_sigmoid(self, X, verbose=False):
        """phonlogy 表現からもっともらしい語彙表現を検索し，その当てはまり度合いから
        語彙表象の値を返すことを考える。
        1. phonolgy 層を sigmoid 変換して phoneme とする
        2. phoneme 表現 と phonology の 6 単語間の交差エントロピー誤差を計算する
        5. 6 単語との交差エントロピー誤差の値から，最小値を引き， (最大値-最小値) で割ることで 0-1 に変換
        6. マイナス 1 を掛けて 上限反転させて +1 することで， 0 から 1 までの値にする
        """
        val = np.finfo(np.float).max     #np で定義済の安全な最大値を代入
        #val = 0.
        name = 'NG'                      #len(words) + 1 語彙名のダミー

        s, lexeme, phoneme = self.split(X)
        lex = np.zeros_like(lexeme)
        phoneme = sigmoid(phoneme)

        for i, word in enumerate(self.phonology):
            loss = CE_loss(phonology[word], phoneme)
            lex[i] = loss
            if loss < val:
                name = word
                val = loss
        lex_diff = lex.max() - lex.min()
        lex = - (lex - lex.min() / lex_diff) + 1.
        return lex

    def fit(self, teacher=None, data=None, verbose=False, nonstop=False):
        """[]
        fit は学習させることを意味するから命名

        引数:
        teacher: np.array
            教師信号。
        data: np.array
            データ。教師信号に近づくように学習させる

        戻り値:
        beta: float
            温度パラメータ
        [loss, pred, mse]
        loss: float
            損失関数の値
        pred: np.array
            予測値
        mse: float
            最小自乗誤差
        """
        if isinstance(teacher, np.ndarray):
            self.teacher = teacher
        elif self.teacher == None:
            print('Set the teacher signal.')
            sys.exit()

        if isinstance(data, np.ndarray):
            self.data = data
        else:
            self.y, self.seriesY = self.one_trial(verbose=verbose)
            #self.data = self.y

        lex_est = self.convert_onset_vowel_coda_2_lexicon(self.y)
        pred = softmax(lex_est)
        loss0 = CE_loss(self.teacher, pred)  #損失関数の初期値
        nonstopped = False

        satu_flag = False

        for i in range(self.iter_max):

            self.y, self.seriesY = self.one_trial(verbose=False)
            lex_est = self.convert_onset_vowel_coda_2_lexicon(self.y)
            pred = softmax(lex_est, beta=self.beta)

            loss = CE_loss(self.teacher, pred)
            if loss0 < (loss + self.epsilon) and (not satu_flag) and (i>0):
                print('iteration saturated at {0:05d} loss:{1:.3f}'.format(i,loss))
                satu_flag = True
            if loss0 > loss:
                loss0 = loss

            if (i % self.interval) == 0:  # 途中結果の出力
                if self.verbose:
                    delta = self.teacher - pred                #教師信号と出力値との差分ベクトル
                    mse = (delta**2).sum()/delta.shape[0]
                    print('t:{0:05d} '.format(i),end='')
                    print('beta:{0:6.3f} decay:{1:.3f} s:{2:.3f} p:{3:.3f}, loss:{4:.3f} pred:{5}'.format(
                        1/self.beta, self.decay, self.s, self.p, loss, pred))
                if (loss0 - loss) < self.epsilon and (i > 0) and (not nonstopped):
                    print('Iteration terminated at {} times'.format(i))
                    return [self.beta, self.decay, self.p, self.s], [loss, pred]

            self.y, self.seriesY = self.one_trial(verbose=verbose)
            lex = self.convert_onset_vowel_coda_2_lexicon(self.y)
            pred = softmax(lex, beta=self.beta)
            delta = self.teacher - pred                #教師信号と出力値との差分ベクトル
            g_delta = delta / (1-pred)

            #beta の更新
            ybar = lex.mean()
            ybar2 = np.dot(pred, lex)
            #Z = np.exp(lex).sum()
            #ydiff = lex - ybar/(Z + self.epsilon)
            ydiff = lex - ybar
            #ydiff = lex - ybar2
            self.beta += self.lr * (g_delta * ydiff).sum()

            s, l, p = self.split(self.seriesY[-1])
            W_pp = self.W[self.pho_slice, self.pho_slice]
            W_lp = self.W[self.lex_slice, self.pho_slice]
            W_sl = self.W[self.sem_slice, self.lex_slice]

            d_delta = np.dot(W_lp.T, g_delta)
            y = np.dot(W_pp,p)
            ybar = y.mean()
            ydiff = y - ybar
            self.decay += self.lr * (d_delta * ydiff).sum()

            l_delta = np.dot(W_lp, d_delta)
            y = np.dot(W_lp,p)
            ybar = y.mean()
            ydiff = y - ybar
            self.p += self.lr * (l_delta * ydiff).sum()

            s_delta = np.dot(W_sl, l_delta)
            y = np.dot(W_lp,p)
            y2 = np.dot(W_sl,y)
            ybar = y2.mean()
            ydiff = y2 - ybar
            #print('546 ydiff.sahpe:', ydiff.shape)
            #print('547 s_delta.shape:', s_delta.shape)
            #print('584 ', s_delta * ydiff)
            #sys.exit()
            self.s += self.lr * (s_delta * ydiff).sum()

            #self.decay -= self.lr * d_decay
            #if self.decay < self.epsilon:
            #    self.decay = self.epsilon
            #for x, y  in zip([self.s, self.p],[d_s, d_p]):
            #    x += self.lr * y

        # 平均自乗誤差の計算
        #mse = (delta**2).sum()/delta.shape[0]
        return [self.beta, self.decay, self.p, self.s], [loss, pred]

#表示桁数の設定
np.set_printoptions(suppress=False, formatter={'float': '{:4.2f}'.format})
#np.set_printoptions(suppress=False, formatter={'float': '{:4.2f}'.format})

teacher = np.copy(Dells_controls)
#teacher = np.array([0.82, 0.04, 0.02, 0.09, 0.01, 0.01])  # L.B
#f = Dell2021(Weights='raw', teacher=teacher, d=0.5, iter_max=10 ** 4, lr=25, IFG_beta=8, f=tanh, verbose=True) # f=ReLU)
model = Dell2021(Weights='raw', teacher=teacher, d=0.5, iter_max=10 ** 4, lr=1, IFG_beta=8, f=tanh, verbose=False) # f=ReLU)

#data = np.array([8.359,2.218,3.285,5.258,0.193,3.504])
#data = np.array([20.840,9.077,6.209,14.118,0.833,6.792])
[beta, decay, s, p], [loss, pred] = model.fit(teacher=teacher, verbose=False)

draw_dell_graph(teacher, pred,
                alabel="Dell(1997)健常者のデータ", acolor='blue',
                blabel="2021 年型 Dell 最新モデル", bcolor='green')

print('          ', list(model.phonology.keys()))
print('teacher:  ', teacher)
print('pred:     ', pred)
print('beta:{0:.3f} decay:{1:.3f} s:{2:.3f} p:{3:.3f}'.format(1/beta, decay, s, p))

```

```python
print('#Dell のデータ, 実データ, wd model, sp model, Foygell and Dell (2000) Table 2 and 4 を参照のこと')
for patient in FoygellDell_tab2_and_4:
    data_n, data_n_wd, data_n_sp = 0, 1, 2
    data = np.array(FoygellDell_tab2_and_4[patient][data_n][:6])
    #model = Dell2021(Weights='raw', teacher=data, d=0.5, iter_max=10 ** 4, lr=1, IFG_beta=8, f=tanh, verbose=False)
    model = Dell2021(Weights='raw', teacher=data, d=0.5, iter_max=10 ** 4, lr=0.5, IFG_beta=8, f=tanh, verbose=False)
    [beta, decay, s, p], [loss, pred] = model.fit(teacher=data, verbose=False)

    #alabel=
    draw_dell_graph(data, pred, title='Foygell and Dell (2000)',
                    alabel='patient:{0} From Foygell & Dell(2000) Tab. 2'.format(patient), acolor='black',
                    blabel='2021年型 新型Dellモデルによる推定値', bcolor='gray',
                    width_inches=6, height_inches=3, fontsize=12)

```

```python
#import gensim.downloader as api
#glove_en = api.load('word2vec-google-news-300', return_path=True)

#DellX = np.zeros((len(lexicons),len(glove_en['cat'])))
#for i, word in enumerate(lexicon):
#    DellX[i] = np.copy(glove_en[word])

#Dell_wordR = np.corrcoef(DellX)
#np.savetxt('Dell_wordR.txt',Dell_wordR)

# Dell の Lexicon 間の相関係数行列。存在しない場合は，このセル上部のコメントをはずして実行
Dell_LexR = np.array([
    [1.000000000000000000e+00, 7.607610875238556281e-01, 1.526896477666510876e-01,
     5.327596034338319964e-01, 7.083070865057317089e-02, -1.112107376916258127e-02],
    [7.607610875238557391e-01, 1.000000000000000000e+00, 1.842294356793724996e-01,
     4.393862362725595161e-01, 6.237472001379058828e-02, 3.078664862324366047e-03],
    [1.526896477666510876e-01, 1.842294356793724719e-01, 1.000000000000000000e+00,
     7.657631000394304888e-02, 8.018105209643067166e-02, 1.477240578677296579e-01],
    [5.327596034338321074e-01, 4.393862362725595716e-01, 7.657631000394306275e-02,
     9.999999999999998890e-01, 6.513470828101816656e-02, -6.153752081466464657e-02],
    [7.083070865057317089e-02, 6.237472001379059522e-02, 8.018105209643067166e-02,
     6.513470828101815269e-02, 1.000000000000000000e+00, 1.396624608082916268e-02],
    [-1.112107376916258127e-02, 3.078664862324366480e-03, 1.477240578677296579e-01,
     -6.153752081466464657e-02, 1.396624608082916268e-02, 9.999999999999997780e-01]])
print(lexicon)
print(Dell_LexR)
```

```python

```


```python
# import what we need
import nltk
from nltk.stem.lancaster import LancasterStemmer ## used to stem words
import tensorflow
import random
import json
stemmer = LancasterStemmer()
import numpy as np
import tensorflow.keras as keras

# printing
from IPython.display import display
import sympy as sp
sp.init_printing(use_latex=True)

# Plotting
import matplotlib.pyplot as plt
%matplotlib inline

# needed for GUI
from tkinter import *
```

```python
# if tensor flow not installed uncomment the below code
# pip install tensorflow
```

<h2> Data Prep and Chatbot Training </h2>

```python
# load json file
with open("intents.json") as file:
    data = json.load(file)
```

```python
# view our json data
data['intents'][0:2]
```

    [{'tag': 'greeting',
      'patterns': ['Hi',
       'How are you',
       'Is anyone there?',
       'Hello',
       'Good day',
       'Whats up'],
      'responses': ['Hello!',
       'Good to see you again!',
       'Hi there, how can I help?'],
      'context_set': ''},
     {'tag': 'goodbye',
      'patterns': ['cya',
       'See you later',
       'Goodbye',
       'I am Leaving',
       'Have a Good day',
       'Bye',
       'Adios'],
      'responses': ['Sad to see you go :(',
       'Talk to you later',
       'Goodbye!',
       'See you later',
       'Have a nice day',
       'Bye! Come back again soon.'],
      'context_set': ''}]

```python
words = []
labels = []

docs_x = []
docs_y = []

# loop through dict to grab our values
for intent in data['intents']:
    for pattern in intent['patterns']:
        # stemming get the root of word, when traiing model
        # only care abt the main meaning of word
        # need to tokenize/split words
        wrds = nltk.word_tokenize(pattern)
        words.extend(wrds) ## add all wrds to list
        docs_x.append(wrds)
        # for each pattern add another item in this var to track what tag its part of
        # each entry in docs_x (tokenized words/wrds) will correspond to (intent) docs_y so we know how to classify
        # each of the patterns
        docs_y.append(intent['tag'])

        if intent['tag'] not in labels:
            labels.append(intent['tag']) # grab all diff tags we need
```

```python
# stem words and remove duplicate words. also lower all words so dont confuse the network into
# thinking spelling case has different meaning
words = [stemmer.stem(w.lower()) for w in words if w!= '?' ] # remove any Question marks, dont want this
words = sorted(list(set(words)))                             # to have any meaning to our model
# sort the labels also
labels = sorted(labels)
```

```python
# prepare data before we feed into the model

# need to convert our text in lists to nums (One-hot-encoding) so our network can
# categorize the data
training = [] # input data goes here
output = []

out_empty = [0 for _ in range(len(labels))]

for x, doc in enumerate(docs_x):

    bag = [] # will be our bag of one hot encoded words
    wrds = [stemmer.stem(w) for w in doc] # stem all the words in patterns

    # go through all diff words in list that are stemmed
    # and add either 1 or 0 to bag of words depending on if this word is in the
    # main words list "(words = sorted(list(set(words))))"
    for w in words:
        # if word here place 1 representing that word exists
        if w in wrds:
            bag.append(1)
        else:
            # word isn't here place 0
            bag.append(0)

    # now generate output that has either 0's or 1's representing the tag that is word
    output_row = out_empty[:]
    # will look through labels list and check where tag is in list and set that value to 1
    # in the output_row
    output_row[labels.index(docs_y[x])] = 1

    # now we have both lists that are now One-hot-encoded !!
    training.append(bag)
    output.append(output_row)

```

```python
# turn lists into numpy array
training = np.array(training)
output = np.array(output)

```

```python
# shape of output
output.shape
```

```python
# shape of training
training.shape
```

```python
# Multi-layer net with ReLU hidden layer

model = keras.models.Sequential()

model.add(keras.layers.Dense(8,input_dim=training.shape[1],activation='relu',
 bias_initializer=keras.initializers.Constant(0.1)))

model.add(keras.layers.Dense(8,activation='relu',
 bias_initializer=keras.initializers.Constant(0.1)))

model.add(keras.layers.Dense(8,activation='relu',
 bias_initializer=keras.initializers.Constant(0.1)))

model.add(keras.layers.Dense(8,activation='relu',
 bias_initializer=keras.initializers.Constant(0.1)))

# Output layer (size 6), softmax activation function
model.add(keras.layers.Dense(output.shape[1],activation='softmax'))
```

```python
# Compile as above (default learning rate and other
# hyperparameters for the Adam optimizer).
# default learning rate for adam optimizer is 0.001

model.compile(loss=keras.losses.CategoricalCrossentropy(),
 optimizer=keras.optimizers.Nadam(learning_rate=0.001),
 metrics=[keras.metrics.CategoricalAccuracy()])

# Display the model
model.summary()
```

    Model: "sequential"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #
    =================================================================
    dense (Dense)                (None, 8)                 1088
    _________________________________________________________________
    dense_1 (Dense)              (None, 8)                 72
    _________________________________________________________________
    dense_2 (Dense)              (None, 8)                 72
    _________________________________________________________________
    dense_3 (Dense)              (None, 8)                 72
    _________________________________________________________________
    dense_4 (Dense)              (None, 21)                189
    =================================================================
    Total params: 1,493
    Trainable params: 1,493
    Non-trainable params: 0
    _________________________________________________________________

```python
keras.utils.plot_model(model,to_file='chatbot.png',
 show_shapes=True,expand_nested=True)
```

```python
# Train it!
history = model.fit(training, output,
 batch_size=1,
 epochs=100,
 verbose=0)
```

```python
# summarize history for loss
plt.plot(history.history['loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['training'], loc='upper left')
plt.show()
```

```python
score = model.evaluate(training,output,verbose=1)
print("Loss:",score[0])
print("Accuracy:",score[1])
```

    5/5 [==============================] - 0s 2ms/step - loss: 0.0021 - categorical_accuracy: 1.0000
    Loss: 0.002149988664314151
    Accuracy: 1.0

```python
# save the model, uncomment this code to save model to in directory
# model.save("model.Chatbot")
```

```python
def bag_of_words(s, words):
    bag = [0 for _ in range(len(words))]

    s_words = nltk.word_tokenize(s)
    s_words = [stemmer.stem(word.lower()) for word in s_words]

    for se in s_words:
        for i, w in enumerate(words):
            if w == se:
                bag[i] = 1

    return np.array(bag)

```

```python
def get_response(msg):

    results = model.predict(np.array([bag_of_words(msg, words)]))[0]
    results_index = np.argmax(results)
    tag = labels[results_index]

    for tg in data["intents"]:
        if tg['tag'] == tag:
            responses = tg['responses']

    return(random.choice(responses))

```

<h2> Chatbot GUI Demo </h2>

```python
BG_GRAY = "#ABB2B9"
BG_COLOR = "#17202A"
TEXT_COLOR = "#EAECEE"

FONT = "Helvetica 14"
FONT_BOLD = "Helvetica 13 bold"

bot_name = "Todd" # set the bot name for the instant message gui
```

```python
class ChatApplication:

    def __init__(self):
        self.window = Tk()
        self._setup_main_window()

    def run(self):
        self.window.mainloop()

    def _setup_main_window(self):
        self.window.title("Chat")
        self.window.resizable(width=False, height=False)
        self.window.configure(width=900, height=550, bg=BG_COLOR)

        # head label
        head_label = Label(self.window, bg=BG_COLOR, fg=TEXT_COLOR,
                           text="Welcome", font=FONT_BOLD, pady=10)
        head_label.place(relwidth=1)

        # tiny divider
        line = Label(self.window, width=450, bg=BG_GRAY)
        line.place(relwidth=1, rely=0.07, relheight=0.012)

        # text widget
        self.text_widget = Text(self.window, width=20, height=2, bg=BG_COLOR, fg=TEXT_COLOR,
                                font=FONT, padx=5, pady=5)
        self.text_widget.place(relheight=0.745, relwidth=1, rely=0.08)
        self.text_widget.configure(cursor="arrow", state=DISABLED)

        # scroll bar
        h_scrollbar = Scrollbar(self.text_widget, orient = 'horizontal')
        h_scrollbar.place()
        h_scrollbar.configure(command=self.text_widget.xview)

        # bottom label
        bottom_label = Label(self.window, bg=BG_GRAY, height=80)
        bottom_label.place(relwidth=1, rely=0.825)

        # message entry box
        self.msg_entry = Entry(bottom_label, bg="#2C3E50", fg=TEXT_COLOR, font=FONT)
        self.msg_entry.place(relwidth=0.74, relheight=0.06, rely=0.008, relx=0.011)
        self.msg_entry.focus()
        self.msg_entry.bind("<Return>", self._on_enter_pressed)

        # send button
        send_button = Button(bottom_label, text="Send", font=FONT_BOLD, width=20, bg=BG_GRAY,
                             command=lambda: self._on_enter_pressed(None))
        send_button.place(relx=0.77, rely=0.008, relheight=0.06, relwidth=0.22)

    def _on_enter_pressed(self, event):
        msg = self.msg_entry.get()
        self._insert_message(msg, "You")

    def _insert_message(self, msg, sender):
        if not msg:
            return

        self.msg_entry.delete(0, END)
        msg1 = f"{sender}: {msg}\n\n"
        self.text_widget.configure(state=NORMAL)
        self.text_widget.insert(END, msg1)
        self.text_widget.configure(state=DISABLED)

        msg2 = f"{bot_name}: {get_response(msg)}\n\n"
        self.text_widget.configure(state=NORMAL)
        self.text_widget.insert(END, msg2)
        self.text_widget.configure(state=DISABLED)

        self.text_widget.see(END)
```

```python
# start the application
if __name__ == "__main__":
    app = ChatApplication()
    app.run()
```

```python

```


# More Realistic Galaxy Populations

Most global 21-cm examples in the documentation tie the volume-averaged emissivity of galaxies to the rate at which mass collapses into dark matter halos (this is the default option in *ARES*). Because of this, they are referred to as $f_{\text{coll}}$ models throughout, and are selected by setting ``pop_sfr_model='fcoll'``. In the code, they are represented by ``GalaxyAggregate`` objects, named as such because galaxies are only modeled in aggregate, i.e., there is no distinction in the properties of galaxies as a function of mass, luminosity, etc.

However, we can also run more detailed models in which the properties of galaxies are allowed to change as a function of halo mass, redshift, and/or potentially other quantities.

A few usual imports before we begin:

```python
%pylab inline
import ares
import numpy as np
import matplotlib.pyplot as pl
```

    Populating the interactive namespace from numpy and matplotlib

## Double Power-Law Star Formation Efficiency

The most common extension to simple models is to allow the star formation efficiency (SFE) to vary as a function of halo mass. This is motivated observationally by the mismatch in the shape of the galaxy luminosity function (LF) and dark matter halo mass function (HMF). In [Mirocha, Furlanetto, & Sun (2017)](http://adsabs.harvard.edu/abs/2017MNRAS.464.1365M>), we adopted a double power-law form for the SFE, i.e.,

\begin{equation}
f_{\ast}(M_h) = \frac{2 f_{\ast,p}} {\left(\frac{M_h}{M_{\text{p}}} \right)^{\gamma_{\text{lo}}} + \left(\frac{M_h}{M_{\text{p}}}  \right)^{\gamma_{\text{hi}}}}
\end{equation}

where the free parameters are the normalization, $f_{\ast,p}$, the peak mass, $M_p$, and the power-law indices in the low-mass and high-mass limits, $\gamma_{\text{lo}}$ and $\gamma_{\text{hi}}$, respectively. Combined with a model for the mass accretion rate onto dark matter halos ($\dot{M}_h$; see next section), the star formation rate as computed as

\begin{equation}
\dot{M}_{\ast} = f_{\ast} \left(\frac{\Omega_{b,0}}{\Omega_{m,0}} \right) \dot{M}_h
\end{equation}

In general, the SFE curve must be calibrated to an observational dataset (see [Fitting to UVLFs](example_mcmc_lf)), but you can also just grab our best-fitting parameters for a redshift-independent SFE curve as follows:

```python
p = ares.util.ParameterBundle('mirocha2017:base')
pars = p.pars_by_pop(0, strip_id=True)
```

The second command extracts only the parameters associated with population #0, which is the stellar population in this calculation (population #1 is responsible for X-ray emission only; see [Models with Multiple Source Populations](example_gs_multipop) for more info on the approach to populations in *ARES*). Passing ``strip_id=True`` removes all ID numbers from parameter names, e.g., ``pop_sfr_model{0}`` becomes ``pop_sfr_model``. The reason for doing that is so we can generate a single ``GalaxyPopulation`` instance, e.g.,

```python
pop = ares.populations.GalaxyPopulation(**pars)
```

If you glance at the contents of ``pars``, you'll notice that the parameters that define the double power-law share a ``pq`` prefix. This is short for "parameterized quantity", and is discussed more generally on the page about the [ParameterizedQuantity object](../uth_pq.html).

**NOTE:** You can access population objects used in a simulation via the ``pops`` attribute, which is a list of population objects that belongs to instances of  common simulation classes like ``Global21cm``, ``MetaGalacticBackground``, etc.

Now, to generate a model for the luminosity function, simply define your redshift of interest and array of magnitudes (assumed to be rest-frame $1600 \unicode{x212B}$ AB magnitudes), and pass them to the aptly named ``LuminosityFunction`` function,

```python
z = 6
MUV = np.linspace(-24, -10)
lf = pop.LuminosityFunction(z, MUV)

pl.semilogy(MUV, lf)
```

To compare to the observed galaxy luminosity function, we can use some convenience routines setup to easily access and plot measurements stored in the *ARES* ``litdata`` module:

```python
obslf = ares.analysis.GalaxyPopulation()
ax = obslf.Plot(z=z, round_z=0.2)
ax.semilogy(MUV, lf)
ax.set_ylim(1e-8, 10)
ax.legend()
```

The ``round_z`` keyword argument makes it so that any dataset available in the range $5.8 \leq z \leq 6.2$ gets included in the plot. To do this for multiple redshifts at the same time, you could do something like:

```python
redshifts = [5,6,7,8]
MUV = np.linspace(-24, -10)

# Create a 1x4 panel plot, include all available data sources
fig, axes = pl.subplots(1, len(redshifts), figsize=(4*len(redshifts), 4))

for i, z in enumerate(redshifts):
    obslf.Plot(z=z, round_z=0.3, ax=axes[i])

    lf = pop.LuminosityFunction(z, MUV)
    axes[i].semilogy(MUV, lf)
    axes[i].annotate(r'$z \simeq %.1f$' % z, (-24, 1e-1))
    axes[i].legend(loc='lower right')
```

To create the ``GalaxyPopulation`` used above from scratch (i.e., without using parameter bundles), we could have just done:

```python
pars = \
{
 'pop_sfr_model': 'sfe-func',
 'pop_sed': 'eldridge2009',

 'pop_fstar': 'pq',
 'pq_func': 'dpl',
 'pq_func_par0': 0.05,
 'pq_func_par1': 2.8e11,
 'pq_func_par2': 0.51,
 'pq_func_par3': -0.61,
 'pq_func_par4': 1e10,  # halo mass at which SFE is normalized
}

pop = ares.populations.GalaxyPopulation(**pars)
```

**NOTE:** Beware that by default, the double power-law is normalized at $M_h = 10^{10} \ M_{\odot}$ (see ``ps_func_par4`` above), whereas the Equation above for $f_{\ast}$ is defined such that ``pq_func_par0`` refers to the SFE at the peak mass. If you prefer a peak-normalized SFE, you can set ``pq_func='dpl_normP'`` instead.

### Accretion Models

By default, *ARES* will derive the mass accretion rate (MAR) onto halos from the HMF itself (see Section 2.2 of [Furlanetto et al. 2017](http://adsabs.harvard.edu/abs/2017MNRAS.472.1576F>) for details). That is, ``pop_MAR='hmf'`` by default. There are also two other options:

* Plug-in your favorite mass accretion model as a lambda function, e.g., ``pop_MAR=lambda z, M: 1. * (M / 1e12)**1.1 * (1. + z)**2.5``.
* Grab a model from ``litdata``. The median MAR from McBride et al. (2009) is included (same as above equation), and can used as ``pop_MAR='mcbride2009'``. If you'd like to add more options, use ``$ARES/input/litdata/mcbride2009.py`` as a guide.

**WARNING:** Note that the MAR formulae determined from numerical simulations may not have been calibrated at the redshifts most often targeted in *ARES* calculations, nor are they guaranteed to be self-consistent with the HMF used in *ARES*. One approach used in [Sun \& Furlanetto (2016)](http://adsabs.harvard.edu/abs/2016MNRAS.460..417S>) is to re-normalize the MAR by requiring its integral to match that predicted by $f_{\text{coll}}(z)$, which can boost the accretion rate at high redshifts by a factor of few. Setting ``pop_MAR_conserve_norm=True`` will enforce this condition in *ARES*.

See [this page](../uth_pop_halo.html) for more information.

```python

```


# BE 240 Lecture 4
# Sub-SBML
## Modeling diffusion, shared resources, and compartmentalized systems

## _Ayush Pandey_

```python
# This notebook is designed to be converted to a HTML slide show
# To do this in the command prompt type (in the folder containing the notebook):
# jupyter nbconvert BE240_Lecture4_Sub-SBML.ipynb --to slides
```

# An example:

### Three different "subsystems" - each with its SBML model
### Another "signal in mixture" subsystem - models signal in the environment / mixture
### Using Sub-SBML we can obtain the combined model for such a system with
* transport across membrane
* shared resources : ATP, Ribosome etc
* resolve naming conflicts (Ribo, Ribosome, RNAP, RNAPolymerase etc.)

# Installing Sub-SBML

```
git clone https://github.com/BuildACell/subsbml.git
```
cd to `subsbml` directory then run the following command to install the package in your environment:
```
 python setup.py install
```

# Dependencies:
1. python-libsbml : Run `pip install python-libsbml`, if you don't have it already. You probably already have this installed as it is also a dependency for bioscrape
1. A simulator: You will need a simulator of your choice to simulate the SBML models that Sub-SBML generates. Bioscrape is an example of a simulator and we will be using that for simulations.

# Update your bioscrape installation
From the bioscrape directory, run the following if you do not have a remote fork (your own Github fork of the original bioscrape repository - `biocircuits/bioscrape`. To list all remote repositories that your bioscrape directory is connected to you can run `git remote -v`. The `origin` in the next two commands corresponds to the biocircuits/bioscrape github repository (you should change it if your remote has a different name)
```
git pull origin master

python setup.py install
```

Update your BioCRNpyler installation as well - if you plan to use your own BioCRNpyler models with Sub-SBML. Run the same commands as for bioscrape from the BioCRNpyler directory.

## Sub-SBML notes:

## On "name" and "identifier":
> SBML elements can have a name and an identifier argument. A `name` is supposed to be a human readable name of the particular element in the model. On the other hand, an `identifier` is what the software tool reads. Hence, `identifier` argument in an SBML model is mandatory whereas `name` argument is optional.

Sub-SBML works with `name` arguments of various model components to figure out what components interact/get combined/shared etc. Bioscrape/BioCRNpyler and other common software tools generate SBML models with `name` arguments added to various components such as species, parameters. As an example, to combine two species, Sub-SBML looks at the names of the two species and if they are the same - they are combined together and given a new identifier but the name remains the same.

## A simple Sub-SBML use case:

A simple example where we have two different models : transcription and translation. Using Sub-SBML, we can combine these two together and run simulations.

```python
# Import statements
from subsbml import createNewSubsystem, createSubsystem

import numpy as np
import pylab as plt
```

## Transcription Model:

Consider the following simple transcription-only model where $G$ is a gene, $T$ is a transcript, and $S$ is the signaling molecule.

We can write the following reduced order dynamics:
1. $G \xrightarrow[]{\rho_{tx}(G, S)} G + T$;
\begin{align}
\rho_{tx}(G, S) = G K_{X}\frac{S^{2}}{K_{S}^{2}+S^{2}}
\\
\end{align}
Here, $S$ is the inducer signal that cooperatively activates the transcription of the gene $G$. Since, this is a positive activation of the gene by the inducer, we have a positive proportional Hill function.

1. $T \xrightarrow[]{\delta} \varnothing$; massaction kinetics at rate $\delta$.

## Translation model:

1. $T \xrightarrow[]{\rho_{tl}(T)} T+X$;
\begin{align}
\rho_{tl}(T) = K_{TR} \frac{T}{K_{R} + T}
\\
\end{align}
Here $X$ is the protein species.
The lumped parameters $K_{TR}$ and $K_R$ model effects due to ribosome saturation. This is the similar Hill function as derived in the enzymatic reaction example.

1. $X \xrightarrow[]{\delta} \varnothing$; massaction kinetics at rate $\delta$.

```python
# Import SBML models by creating Subsystem class objects
ss1 = createSubsystem('transcription_SBML_model.xml')
ss2 = createSubsystem('translation_SBML_model.xml')

ss1.renameSName('mRNA_T', 'T')
# Combine the two subsystems together
tx_tl_subsystem = ss1 + ss2

# The longer way to do the same thing:

# tx_tl_subsystem = createNewSubsystem()
# tx_tl_subsystem.combineSubsystems([ss1,ss2], verbose = True)

# Set signal concentration (input) - manually and get ID for protein X

X_id = tx_tl_subsystem.getSpeciesByName('X').getId()
# Writing a Subsystem to an SBML file (Export SBML)
_ = tx_tl_subsystem.writeSBML('txtl_ss.xml')
tx_tl_subsystem.setSpeciesAmount('S',10)
```

    True

```python
# Simulate with Bioscrape and plot the result
timepoints = np.linspace(0,100,100)
results, _ = tx_tl_subsystem.simulateWithBioscrape(timepoints)
plt.plot(timepoints, results[X_id], linewidth = 3, label = 'S = 10')
tx_tl_subsystem.setSpeciesAmount('S',5)
results, _ = tx_tl_subsystem.simulateWithBioscrape(timepoints)
plt.plot(timepoints, results[X_id], linewidth = 3, label = 'S = 5')
plt.title('Protein X dynamics')
plt.ylabel('[X]')
plt.xlabel('Time')
plt.legend()
plt.show()
```

```python
# Viewing the change log for the changes that Sub-SBML made

# print(ss1.changeLog)
# print(ss2.changeLog)
print(tx_tl_subsystem.changeLog)
```

    {'default_bioscrape_generated_model_937333': 'default_bioscrape_generated_model_374940_combined', 'default_bioscrape_generated_model_374940': 'default_bioscrape_generated_model_374940_combined', 'mRNA_T_bioscrape_generated_model_374940': 'mRNA_T_bioscrape_generated_model_374940_1_combined', 'T_bioscrape_generated_model_937333': 'mRNA_T_bioscrape_generated_model_374940_1_combined', 'n_bioscrape_generated_model_374940': 'n_combined', 'n_bioscrape_generated_model_937333': 'n_combined', 'delta_bioscrape_generated_model_374940': 'delta_combined', 'delta_bioscrape_generated_model_937333': 'delta_combined'}

## Signal induction model:

1. $\varnothing \xrightarrow[]{\rho(I)} S$;
\begin{align}
\rho(S) = K_{0} \frac{I^2}{K_{I} + I^2}
\\
\end{align}
Here $S$ is the signal produced on induction by an inducer $I$.
The lumped parameters $K_{0}$ and $K_S$ model effects of cooperative production of the signal by the inducer. This is the similar Hill function as derived in the enzymatic reaction example.

```python
ss3 = createSubsystem('signal_in_mixture.xml')

# Signal subsystem (production of signal molecule)
combined_ss = ss1 + ss2 + ss3

# Alternatively
combined_ss = createNewSubsystem()
combined_ss.combineSubsystems([ss1,ss2,ss3])

# Writing a Subsystem to an SBML file (Export SBML)
combined_ss.writeSBML('txtl_combined.xml')

# Set signal concentration (input) - manually and get ID for protein X
combined_ss.setSpeciesAmount('I',10)
X_id = combined_ss.getSpeciesByName('X').getId()
```

```python

# Simulate with Bioscrape and plot the result
timepoints = np.linspace(0,100,100)
results, _ = combined_ss.simulateWithBioscrape(timepoints)
plt.plot(timepoints, results[X_id], linewidth = 3, label = 'I = 10')
combined_ss.setSpeciesAmount('I',2)
results, _ = combined_ss.simulateWithBioscrape(timepoints)
plt.plot(timepoints, results[X_id], linewidth = 3, label = 'I = 5')
plt.title('Protein X dynamics')
plt.ylabel('[X]')
plt.xlabel('Time')
plt.legend()
plt.show()
```

```python
combined_ss.changeLog
```

    {'default_bioscrape_generated_model_937333': 'default_bioscrape_generated_model_374940_combined',
     'default_bioscrape_generated_model_116519': 'default_bioscrape_generated_model_374940_combined_1',
     'default_bioscrape_generated_model_374940': 'default_bioscrape_generated_model_374940_combined_1',
     'mRNA_T_bioscrape_generated_model_374940': 'mRNA_T_bioscrape_generated_model_374940_1_combined',
     'T_bioscrape_generated_model_937333': 'mRNA_T_bioscrape_generated_model_374940_1_combined',
     'S_bioscrape_generated_model_374940': 'S_bioscrape_generated_model_374940_1_combined',
     'S_bioscrape_generated_model_116519': 'S_bioscrape_generated_model_374940_1_combined',
     'KI_bioscrape_generated_model_374940': 'KI_combined',
     'n_bioscrape_generated_model_374940': 'n_combined',
     'n_bioscrape_generated_model_937333': 'n_combined',
     'n_bioscrape_generated_model_116519': 'n_combined',
     'delta_bioscrape_generated_model_374940': 'delta_combined',
     'delta_bioscrape_generated_model_937333': 'delta_combined'}

## What does Sub-SBML look for?

1. For compartments: if two compartments have the same `name` and the same `size` attributes => they are combined together.
1. For species: if two species have the same `name` attribute => they are combined together. If initial amount is not the same, the first amount is set. It is easy to set species amounts later.
1. For parameters: if two paraemters have the same `name` attribute **and** the same `value` => they are combined together.
1. For reactions: if two reactions have the same `name` **and** the same reaction string (reactants -> products) => they are combined together.
1. Other SBML components are also merged.

# Utility functions for Subsystems
1. Set `verbose` keyword argument to `True` to get a list of detailed warning messages that describe the changes being made to the models. Helpful in debugging and creating clean models when combining multiple models.
1. Use `renameSName` method for a `Subsystem` to rename any species' names throughout a model and `renameSIdRefs` to rename identifiers.
1. Use `createBasicSubsystem()` function to get a basic "empty" subsystem model.
1. Use `getSpeciesByName` to get all species with a given name in a Subsystem model.
1. use `shareSubsystems` method similar to `combineSubsystems` method if you are only interested in getting a model with shared resource species combined together.
1. Set `combineNames` keyword argument to `False` in `combineSubsystems` method to combine the Subsystem objects but treating the elements with the same `name` as different.

# Modeling transport across membranes

## System 1 : TX-TL with IPTG reservoir and no membrane

```python
from subsbml import System, createSubsystem, combineSystems, createNewSubsystem

cell_1 = System('cell_1')

ss1 = createSubsystem('txtl_ss.xml')
ss1.renameSName('S', 'IPTG')
ss2 = createSubsystem('IPTG_reservoir.xml')

IPTG_external_conc = ss2.getSpeciesByName('IPTG').getInitialConcentration()

cell_1.setInternal([ss1])
cell_1.setExternal([ss2])
# cell_1.setMembrane() # Membrane-less system
ss1.setSpeciesAmount('IPTG', IPTG_external_conc)

cell_1_model = cell_1.getModel() # Get a Subsystem object that represents the combined model for cell_1
cell_1_model.writeSBML('cell_1_model.xml')
```

    1

## System 2 : TX-TL with IPTG reservoir and a simple membrane

### Membrane : IPTG external and internal diffusion in a one step reversible reaction

```python
from subsbml import System, createSubsystem, combineSystems, createNewSubsystem
ss1 = createSubsystem('txtl_ss.xml')
ss1.renameSName('S','IPTG')
ss2 = createSubsystem('IPTG_reservoir.xml')

# Create a simple IPTG membrane where IPTG goes in an out of the membrane via a reversible reaction
mb2 = createSubsystem('membrane_IPTG.xml', membrane = True)

# cell_2 =  System('cell_2',ListOfInternalSubsystems = [ss1],
#                  ListOfExternalSubsystems = [ss2],
#                  ListOfMembraneSubsystems = [mb2])
cell_2 = System('cell_2')
cell_2.setInternal(ss1)
cell_2.setExternal(ss2)
cell_2.setMembrane(mb2)
cell_2_model = cell_2.getModel()
cell_2_model.setSpeciesAmount('IPTG', 1e4, compartment = 'cell_2_external')
cell_2_model.writeSBML('cell_2_model.xml')
```

    The subsystem from membrane_IPTG.xml has multiple compartments

    1

## System 3 : TX-TL with IPTG reservoir and a detailed membrane diffusion

### Membrane : IPTG external binds to a transport protein and forms a complex. This complex causes the diffusion of IPTG in the internal of the cell.

```python
# Create a more detailed IPTG membrane where IPTG binds to an intermediate transporter protein, forms a complex
# then transports out of the cell system to the external environment

mb3 = createSubsystem('membrane_IPTG_detailed.xml', membrane = True)

cell_3 =  System('cell_3',ListOfInternalSubsystems = [ss1],
                 ListOfExternalSubsystems = [ss2],
                 ListOfMembraneSubsystems = [mb3])
cell_3_model = cell_3.getModel()
cell_3_model.setSpeciesAmount('IPTG', 1e4, compartment = 'cell_3_external')
cell_3_model.writeSBML('cell_3_model.xml')

combined_model = combineSystems([cell_1, cell_2, cell_3])
```

    The subsystem from membrane_IPTG_detailed.xml has multiple compartments

```python
import numpy as np
import pylab as plt
timepoints = np.linspace(0,2,100)
results_1, _ = cell_1_model.simulateWithBioscrape(timepoints)
results_2, _ = cell_2_model.simulateWithBioscrape(timepoints)
results_3, _ = cell_3_model.simulateWithBioscrape(timepoints)

X_id1 = cell_1_model.getSpeciesByName('X').getId()
X_id2 = cell_2_model.getSpeciesByName('X', compartment = 'cell_2_internal').getId()
X_id3 = cell_3_model.getSpeciesByName('X', compartment = 'cell_3_internal').getId()
plt.plot(timepoints, results_1[X_id1], linewidth = 3, label = 'No membrane')
plt.plot(timepoints, results_2[X_id2], linewidth = 3, label = 'Simple membrane')
plt.plot(timepoints, results_3[X_id3], linewidth = 3, label = 'Advanced membrane')
plt.xlabel('Time')
plt.ylabel('[X]')
plt.legend()
plt.show()

timepoints = np.linspace(0,200,100)
results_1, _ = cell_1_model.simulateWithBioscrape(timepoints)
results_2, _ = cell_2_model.simulateWithBioscrape(timepoints)
results_3, _ = cell_3_model.simulateWithBioscrape(timepoints)
X_id1 = cell_1_model.getSpeciesByName('X').getId()
X_id2 = cell_2_model.getSpeciesByName('X', compartment = 'cell_2_internal').getId()
X_id3 = cell_3_model.getSpeciesByName('X', compartment = 'cell_3_internal').getId()
plt.plot(timepoints, results_1[X_id1], linewidth = 3, label = 'No membrane')
plt.plot(timepoints, results_2[X_id2], linewidth = 3, label = 'Simple membrane')
plt.plot(timepoints, results_3[X_id3], linewidth = 3, label = 'Advanced membrane')
plt.xlabel('Time')
plt.ylabel('[X]')
plt.legend()
plt.show()
```

# Additional Sub-SBML Tools:

* Create SBML models directly using `SimpleModel` class
* Simulate directly using `bioscrape` or `libRoadRunner` with various simulation options
* Various utility functions to edit SBML models:
    1. Change species names/identifiers throughout an SBML model.
    1. Edit parameter values or species initial conditions easily (directly in an SBML model).
* `combineSystems` function can be used to combine multiple `System` objects together as shown in the previous cell. Also, a special use case interaction modeling function is available : `connectSubsystems`. Refer to the tutorial_interconnetion.ipynb notebook in the tutorials directory for more information about this.

# Things to Try:
1. Compartmentalize your own SBML model - generate more than 1 model each with a different compartment names. Using tools in this notebook, try to combine your models together and regenerate the expected simulation.
1. Implement a diffusion model and use it as a membrane model for a `System` of your choice.
1. Implement an even more complicated diffusion model for the above example and run the simulation.
1. **The package has not been tested extensively. So, it would be really great if you could raise [issues](https://github.com/BuildACell/subsbml/issues) on Github if you face any errors with your models. Also, feel free to send a message on Slack channel or DM.**

```python

```