炭酸イオンかどうかの判定アルゴリズムの作成（ログ）

## 目標
POSCARファイルから，炭酸イオンを含むどうかを判定するアルゴリズムを作成する．


## 前準備
0-1．元素種C, Oを含むPOSCARファイルから，POSCAR.nnlistを作成し，CとOの結合距離の分布図を描く．
0-2．1．より炭酸イオンのCO間の結合距離の最大値を推定する．<sup>（注）</sup>
（注）アルゴリズムには直接関係ないため，必須でない．

## 結合探索アルゴリズム
1．POSCARファイルが元素種C, Oを含む
2．POSCAR.nnlistにおいて，原子Cから0-2．のCO結合距離以内に，原子Cに対するNeigborsListに原子Oを3つ以上含む，中心原子Cが存在する．
<!--
2．POSCAR.nnlistにおいて，原子Cから0-2．のCO結合距離以内に，原子Oを3つ以上含む，中心原子Cが存在する．
-->
####### 条件１：原子Cから原子Oに対して，結合手がちょうど3本生えているか？ #######
3．2．の中心原子Cに対して，
1番近い原子がOであり，かつ2番目に近い原子もOであり，かつ3番目に近い原子もOである，中心原子Cが存在する．
4．2．の中心原子Cに対して，4番目に近い原子が
存在しない場合 → 条件１をクリア．
存在する場合 → 5．に進む．
5．2．の中心原子Cに対して，4番目に近い原子とCとの距離が，3番目に近い原子（：酸素O ∵手順3．）とのCO距離より大きい．
→ 条件1をクリア．
<!--
5．2．の中心原子Cに対して4番目に近い原子が，0-2．で推定されるCO間結合の最大値より大きい．
-->


####### 条件2：C周りの3つのOすべてが，CO結合距離内において，中心のC以外と結合していないか？ #######
6．3．の3つの原子O全てに対して，3．の中心の原子Cとの距離以内に，中心原子C以外の別の原子が存在しない．
→ 条件2をクリア．

→ 「条件1かつ条件2を満たす．⇔ 炭酸イオンを含む」．？



## 前準備ためのプログラムの実行（流れ）
* 0-1-1．cif/ディレクトリ内の，元素種C, Oを含むPOSCARファイルパスをリストで取得．

元素種C，Oを含むPOSCARファイルのパスのみ抽出（ログ） #Python3 - Qiita

https://qiita.com/k-morii-toridai/items/1583fb78e1fde205cebc


* 0-1-2．0-1-1．で得たパスリストに対して，
poscar2nnlistを用いて，5Å以内のネイバーリスト（：POSCAR.nnlist）を作成．

POSCARからneighbors listの作成（ログ） #Python3 - Qiita

https://qiita.com/k-morii-toridai/items/ba2179dc31e376846ec6


* 0-1-3．0-1-2．で得たPOSCAR.nnlistから，原子Cと原子Oの距離を抽出し，NumPyのndarrayに格納．
（注）POSCAR.nnlistはPandasのDataFrame化して処理する．



* 0-1-4．0-1-3．で得たndarrayを1次元にflattenし，ヒストグラムを描画．

![Histgram_of_distances_between_carbon_and_oxygen_100dpi_s5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3487151/f14c7c00-2141-5f26-e276-df66f3a07b8d.png)

![Histgram_of_distances_between_carbon_and_oxygen_under200pm_100dpi_s5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3487151/592cd18a-0ca9-3ef9-c3f2-4c7f6ba11bb4.png)



* 0-2．0-1-4．より炭酸イオンのCO間の結合距離の最大値を推定する．
ヒストグラムから，炭素酸素間結合距離の閾値として，1.6Åとするのが妥当だと考えられる．
一方で，炭酸イオンの熱化学半径は1.64Åと書かれている．
これらより，目安となる炭素酸素間結合距離の最大値を1.65Åと仮定して進める．



## アルゴリズムの実装
```terminal
pwd
/mnt/ssd_elecom_c2c_960gb/scripts
mkdir algorithm_bond_search
cd algorithm_bond_search
```
まず，サンプルのPOSCAR.nnlistファイルをダウンロードする．
```terminal
sudo git clone https://github.com/k-morii-toridai/sample_test_files.git
ls -CF sample_test_files/
1000033.cif*  POSCAR*  POSCAR.nnlist*
```
続いて，自作のファイル変換パッケージをダウンロードする．
```terminal
sudo git clone https://github.com/k-morii-toridai/package_file_conversion.git
ls -CF package_file_conversion/
__init__.py*  __pycache__/  df2poscar.py*  nnlist2df.py*  poscar2df.py*  textfile2df.py*
```

炭酸イオンを含むか判定のalgolithmを作成した．
```python
import numpy as np


def filter_2(df_nnlist):
    """
    2．POSCAR.nnlistにおいて，原子Cから0-2．のCO結合距離以内に，原子Oを3つ以上含む，中心原子Cが存在するかどうか判定．
    → 存在する場合、True値，{中心原子Cの'central_atom_id': そのneighborsの'central_atom_id'}の辞書の2つを返す．
    → 存在しない場合，False値，空の辞書を返す．

    Usage:
    ------
    bool_2, dict_2 = filter_2(df_nnlist=df_nnlist)

    Parameters:
    -----------
    df_nnlist: pd.DataFrame

    Returns:
    --------
    bool_2: bool
    dict_2: dict
    """
    df_nnlist_group_dict = df_nnlist[df_nnlist['central_atom_symbol'] == 'C'].groupby('central_atom_id').groups
    df_nnlist_central_atom_ids = np.array(list(df_nnlist_group_dict.keys()))
    bool_list = []
    for key in df_nnlist_central_atom_ids:
        bool_list.append(df_nnlist.iloc[df_nnlist_group_dict[key]]['neighboring_atom_symbol'].tolist().count('O') >= 3)
    df_nnlist_central_atom_ids_fillterd = df_nnlist_central_atom_ids[bool_list]
    bool_filter_2 = len(df_nnlist_central_atom_ids_fillterd) >= 1
    filtered_df_nnlist_group_dict = {key: df_nnlist_group_dict[key] for key in df_nnlist_group_dict.keys() if key in df_nnlist_central_atom_ids_fillterd}

    return bool_filter_2, filtered_df_nnlist_group_dict


def filter_3(df_nnlist, dict_2):
    """
    3．2．の中心原子Cに対して，1番近い原子がOであり，かつ2番目に近い原子もOであり，かつ3番目に近い原子もOである，中心原子Cが存在するかどうか判定．
    → 存在する場合，True値，{中心原子Cの'central_atom_id': そのneighborsの'central_atom_id'}の辞書の2つを返す．
    → 存在しない場合，False値，空の辞書を返す．

    Usage:
    ------
    bool_3, dict_3 = filter_3(df_nnlist=df_nnlist, dict_2=dict_2)

    Parameters:
    -----------
    df_nnlist: pd.DataFrame
    dict_2: dict

    Returns:
    --------
    bool_3: bool
    dict_3: dict
    """
    bool_list_3 = []
    for k, v in dict_2.items():
        bool_list_3.append(set(df_nnlist.iloc[dict_2[k]].sort_values(by='rel_distance')['neighboring_atom_symbol'].tolist()[1:4]) == {'O'})
    df_nnlist_central_atom_ids_fillterd_3 = np.array(list(dict_2.keys()))[bool_list_3]
    filtered_3_df_nnlist_group_dict = {key: dict_2[key] for key in dict_2.keys() if key in df_nnlist_central_atom_ids_fillterd_3}
    bool_filter_3 = len(df_nnlist_central_atom_ids_fillterd_3) >= 1

    return bool_filter_3, filtered_3_df_nnlist_group_dict


def filter_4(df_nnlist, dict_3):
    """
    4．2．の中心原子Cに対して4番目に近い原子が存在しないかを判定．
    → 存在しない場合，True値，{中心原子Cの'central_atom_id': そのneighborsの'central_atom_id'}の辞書の2つを返す．
    → 存在する場合，False値，空の辞書の2つを返す．

    Usage:
    ------
    bool_4, dict_4 = filter_4(df_nnlist=df_nnlist, dict_3=dict_3)

    Parameters:
    -----------
    df_nnlist: pd.DataFrame
    dict_3: dict

    Returns:
    --------
    bool_4: bool
    dict_4: dict
    """
    bool_list_4 = []
    for k, v in dict_3.items():
        bool_list_4.append(len(df_nnlist.iloc[dict_3[k]].sort_values(by='rel_distance')['neighboring_atom_symbol'].tolist()) == 4)
    df_nnlist_central_atom_ids_fillterd_4 = np.array(list(dict_3.keys()))[bool_list_4]
    filtered_4_df_nnlist_group_dict = {key: dict_3[key] for key in dict_3.keys() if key in df_nnlist_central_atom_ids_fillterd_4}
    bool_filter_4 = len(df_nnlist_central_atom_ids_fillterd_4) >= 1

    return bool_filter_4, filtered_4_df_nnlist_group_dict


def filter_5(df_nnlist, dict_3):
    """
    5．2．の中心原子Cに対して4番目に近い原子が，Cに3番目に近い原子OとCのCO距離より大きいCが存在するどうかを判定．
    → 存在する場合，True値，{中心原子Cの'central_atom_id': そのneighborsの'central_atom_id'}の辞書の2つを返す．
    → 存在しない場合，False値，空の辞書を返す．

    Usage:
    ------
    bool_5, dict_5 = filter_5(df_nnlist=df_nnlist, dict_3=dict_3)

    Parameters:
    -----------
    df_nnlist: pd.DataFrame
    dict_3: dict

    Returns:
    --------
    bool_5: bool
    dict_5: dict
    """
    bool_list_5 = []
    for k, v in dict_3.items():
        third_CO_bond_dist = df_nnlist.iloc[dict_3[k]].sort_values(by='rel_distance')['rel_distance'].tolist()[3]
        forth_dist = df_nnlist.iloc[dict_3[k]].sort_values(by='rel_distance')['rel_distance'].tolist()[4]
        bool_list_5.append(third_CO_bond_dist < forth_dist)
    df_nnlist_central_atom_ids_fillterd_5 = np.array(list(dict_3.keys()))[bool_list_5]
    filtered_5_df_nnlist_group_dict = {key: dict_3[key] for key in dict_3.keys() if key in df_nnlist_central_atom_ids_fillterd_5}
    bool_filter_5 = len(df_nnlist_central_atom_ids_fillterd_5) >= 1

    return bool_filter_5, filtered_5_df_nnlist_group_dict


def filter_6(df_nnlist, dict_3):
    """
    6．3．の3つの原子O全てに対して，3．の中心の原子Cとの距離以内に，中心原子C以外の別の原子が存在しないかどうかを判定．
    → 存在しない場合，True値，中心原子Cの'central_atom_id'のndarrayの2つを返す．
    → 存在する場合，False値，空のndarrayを返す．

    Usage:
    ------
    bool_6, C_ids = filter_6(df_nnlist=df_nnlist, dict_3=dict_3)

    Parameters:
    -----------
    df_nnlist: pd.DataFrame
    dict_3: dict

    Returns:
    --------
    bool_6: bool
    C_ids: ndarray
    """
    bool_list_6 = []
    for k, v in dict_3.items():
        # C周りのO３つのindex
        indices = df_nnlist.iloc[dict_3[k]].sort_values(by='rel_distance').index[1:4]
        O_ids = df_nnlist.iloc[indices].apply(lambda row: row['neighboring_atom_id'], axis=1).tolist()
        bool_list_temp = []
        for O_id in O_ids:
            bool_temp = df_nnlist[df_nnlist['central_atom_id'] == O_id].sort_values('rel_distance')['neighboring_atom_symbol'].tolist()[1] == 'C'
            bool_list_temp.append(bool_temp)
        if set(bool_list_temp) == {True}:
            bool_list_6.append(True)
        else:
            bool_list_6.append(False)
    C_ids = np.array(list(dict_3.keys()))[bool_list_6]
    bool_filter_6 = bool_list_6.count(True) >= 1

    return bool_filter_6, C_ids


def concat_filter(df_nnlist):
    """
    filter_2()~filter_6()の関数を用いて，POSCAR.nnlistを用いて，POSCARファイルに炭酸イオンを含むかどうかの判定algolismを作成．
    → True値が返された場合，炭酸イオンを含む．
    → False値が返され場合，炭酸イオンを含まない．

    Usage:
    ------
    concat_filter(df_nnlist=df_nnlist)

    Parameters:
    -----------
    df_nnlist: pd.DataFrame

    Returns:
    --------
    bool: True or False
    """
    bool_2, dict_2 = filter_2(df_nnlist=df_nnlist)
    if bool_2:
        bool_3, dict_3 = filter_3(df_nnlist=df_nnlist, dict_2=dict_2)
        if bool_3:
            bool_4, dict_4 = filter_4(df_nnlist=df_nnlist, dict_3=dict_3)
            if bool_4:
                return True
            else:
                bool_5, dict_5 = filter_5(df_nnlist=df_nnlist, dict_3=dict_3)
                if bool_5:
                    bool_6, C_ids = filter_6(df_nnlist=df_nnlist, dict_3=dict_3)
                    if bool_6:
                        return True
                    else:
                        return False
                else:
                    return False
        else:
            return False
    else:
        return False


if __name__ == '__main__':
    from package_file_conversion.nnlist2df import nnlist2df
    nnlist_path = 'sample_test_files/1000033/nnlist_5/POSCAR.nnlist'
    df_nnlist = nnlist2df(nnlist_path=nnlist_path)
    print(f"concat_filter(df_nnlist=df_nnlist): {concat_filter(df_nnlist=df_nnlist)}")
```


```python
concat_filter(df_nnlist=df_nnlist)
```
炭酸イオンを含むと判定した場合は，True値を
含まないと判定した場合は，False値を返す．













<!--  comment out

## アルゴリズム
1．POSCARファイルが元素種C, Oを含む
2．POSCAR.nnlistにおいて，原子Cの周りに原子Oを3つ以上含む，中心原子Cが存在する．
########## 原子Cから原子Oに対して，手がちょうど3本生えているか？ ##########
3．2．の中心原子Cに対して，
1番近い原子がOであり，かつ2番目に近い原子もOであり，かつ3番目に近い原子もOである，原子Cが存在する．
4．3．の原子Oそれぞれに対し，中心原子Cとの距離が，CO結合距離以下である．
注）CO結合距離は，理論値より少し大きめにとる．
5．2．の中心原子Cに対して，
4番目に近い原子が，4．の３番目に近い原子OとのCO距離より大きい．



4．3．の原子Cに対して，原子Oとの距離が，CO結合の距離（：少し大きめにとる）以下の原子Oがちょうど3つ存在する．
5．2．の中心原子Cに対して4番目に近い原子が，2. の３番目に近い原子OとのCO距離より大きい．


########## 原子Hなどが結合した原子Oを削除（炭酸水素イオンになるから） ##########
6．3．の3つの原子O全てに対して，3．の中心の原子Cとの距離以内に，中心原子C以外の別の原子が存在しない．

--!>