"""
paw_structure.ion
-----------------
Ion complex detection using geometric :ref:`algorithm<Control_ION_algorithm>`.

Main routine is :func:`.ion_find_parallel`.

Dependencies:
    :py:mod:`functools`
    :py:mod:`miniutils`
    :py:mod:`numpy`
    :py:mod:`pandas`
    :mod:`.neighbor`
    :mod:`.utility`
    :class:`.Snap`

.. autosummary::

      ion_find_parallel
      ion_load
      ion_save
      ion_single
"""

import numpy as np
import pandas as pd
from functools import partial
import miniutils.progress_bar as progress
# MODULES WITHIN PROJECT
from . import neighbor
from . import utility
from .tra import Snap



########################################################################################################################
# FIND ION COMPLEX FOR A SINGLE SNAPSHOT
########################################################################################################################
# INPUT
# class Snap snap   snapshot containing all information
# str id1           identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2           identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3           identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1        cutoff distance for first neighbor search
# float cut2        cutoff distance for second neighbor search
#####
# OUTPUT
# pandas DataFrame  contains the whole complex centered around id1
########################################################################################################################
def ion_single(snap, id1, id2, id3, cut1, cut2):
    """
    Find ion complex of a single snapshot of atomic positions.

    Args:
        snap (:class:`.Snap`): single snapshot containing the atomic information
        id1 (str): identifier for atom used as center (e.g. 'MN')
        id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')
        id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')
        cut1 (float): cutoff distance for first neighbor search
        cut2 (float): cutoff distance for second neighbor search

    Returns:
        :class:`.Snap`: snapshot containing an ion complex

    Todo:
        Implement possibility for more atoms of type id1 or allow selection by name.
    """
    # check if only one atom is selected as ion
    if len(snap.atoms[snap.atoms['id'] == id1]) != 1:
        utility.err('ion_single', 0, [len(snap.atoms[snap.atoms['id'] == id1])])
    # check if all three are different species
    if id1 == id2 or id2 == id3 or id1 == id3:
        utility.err('ion_single', 1, [id1, id2, id3])
    # search first neighbors
    next1 = neighbor.neighbor_name(snap, id1, id2, cut1)
    # extract name lists
    id1_list = [atom[0] for atom in next1]
    id2_list = [y for x in [atom[1:] for atom in next1] for y in x]
    # search second neighbors
    next2 = neighbor.neighbor_name(snap, id2, id3, cut2, names=id2_list)
    # extract name list
    id3_list = [y for x in [atom[1:] for atom in next2] for y in x]
    # extract correct atom information
    id1_list = snap.atoms.loc[snap.atoms['name'].isin(id1_list)]
    id2_list = snap.atoms.loc[snap.atoms['name'].isin(id2_list)]
    id3_list = snap.atoms.loc[snap.atoms['name'].isin(id3_list)]
    comp = pd.concat([id1_list, id2_list, id3_list])
    return Snap(snap.iter, snap.time, snap.cell, None, None, dataframe=comp)


########################################################################################################################
# SAVE INFORMATION FROM ion_find TO FILE <root>.ext FOR LATER ANALYSIS
# TODO: check if snapshots is empty
########################################################################################################################
# INPUT
# str root                      root name for saving file
# list class Snap snapshots     list with information to be saved
# str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2                       identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1                    cutoff distance for first neighbor search
# float cut2                    cutoff distance for second neighbor search
# str ext (optional)            extension for the saved file: name = root + ext
########################################################################################################################
def ion_save(root, snapshots, id1, id2, id3, cut1, cut2, ext='.ion'):
    """
    Save results to file :ref:`Output_ion`.

    Args:
        root (str): root name for saving file
        snapshots (list[:class:`.Snap`]): list of snapshots containing an ion complex
        id1 (str): identifier for atom used as center (e.g. 'MN')
        id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')
        id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')
        cut1 (float): cutoff distance for first neighbor search
        cut2 (float): cutoff distance for second neighbor search
        ext (str, optional): default ".ion" - extension for the saved file: name = root + ext

    Todo:
        Check if snapshots is empty.
    """
    # open file
    path = root + ext
    try:
        f = open(path, 'w')
    except IOError:
        utility.err_file('ion_save', path)
    # write header
    f.write(utility.write_header())
    f.write("ION COMPLEXES\n")
    f.write("%-14s%14.8f\n" % ("T1", snapshots[0].time))
    f.write("%-14s%14.8f\n" % ("T2", snapshots[-1].time))
    f.write("%-14s%14d\n" % ("SNAPSHOTS", len(snapshots)))
    f.write("%-14s%14s\n" % ("ID1", id1))
    f.write("%-14s%14s\n" % ("ID2", id2))
    f.write("%-14s%14s\n" % ("ID3", id3))
    f.write("%-14s%14.8f\n" % ("CUT1", cut1))
    f.write("%-14s%14.8f\n" % ("CUT2", cut2))
    f.write("%-14s\n" % ("UNIT CELL"))
    np.savetxt(f, snapshots[0].cell, fmt="%14.8f")
    # write structure information
    for i in range(len(snapshots)):
        f.write("-" * 84 + "\n")
        f.write("%-14s%-14.8f%-14s%-14d%-14s%-14d\n" %
                ("TIME", snapshots[i].time, "ITERATION", snapshots[i].iter, "ATOMS", len(snapshots[i].atoms)))
        f.write("%-14s%-14s%-14s%14s%14s%14s\n" % ('NAME', 'ID', 'INDEX', 'X', 'Y', 'Z'))
        np.savetxt(f, snapshots[i].atoms, fmt="%-14s%-14s%-14d%14.8f%14.8f%14.8f")
    f.close()
    return


########################################################################################################################
# LOAD INFORMATION PREVIOUSLY SAVED BY ion_save()
# WARNING: READING IS LINE SENSITIVE! ONLY USE ON UNCHANGED FILES WRITTEN BY ion_save()
########################################################################################################################
# INPUT
# str root                      root name for the file to be loaded
# str ext (optional)            extension for the file to be loaded: name = root + ext
#####
# OUTPUT
# list class Snap snapshots     list of all information
########################################################################################################################
def ion_load(root, ext='.ion'):
    """
    Load information from the :ref:`Output_ion` file previously created by :func:`.ion_save`.

    Args:
        root (str): root name for the file to be loaded
        ext (str, optional): default ".ion" - extension for the file to be loaded: name = root + ext

    Returns:
        list[:class:`.Snap`]: list of snapshots containing an ion complex

    Note:
        Reading is line sensitive. Do not alter the output file before loading.
    """
    path = root + ext
    try:
        f = open(path, 'r')
    except IOError:
        utility.err_file('ion_load', path)
    text = f.readlines()  # read text as lines
    for i in range(len(text)):
        text[i] = text[i].split()  # split each line into list with strings as elements
    snapshots = []  # storage list
    for i in range(len(text)):
        if len(text[i]) > 1:
            if text[i][0] == 'UNIT':
                cell = np.array(text[i+1:i+4], dtype=float) # get unit cell
            if text[i][0] == "TIME":  # search for trigger of new snapshot
                iter = int(text[i][3])
                time = float(text[i][1])
                n_atoms = int(text[i][5])
                test = np.array(text[i + 2:i + 2 + n_atoms])
                atoms = {}
                atoms['name'] = test[:, 0]
                atoms['id'] = test[:, 1]
                atoms['index'] = np.array(test[:, 2], dtype=int)
                df = pd.DataFrame(data=atoms)
                # save information as class Snap
                snapshots.append(Snap(iter, time, cell, np.array(test[:, 3:6], dtype=np.float64), df))
    return snapshots


########################################################################################################################
# FIND ION COMPLEXES IN MULTIPLE SNAPSHOTS
# WARNING: NOT IN USE BECAUSE NO PARALLEL COMPUTING
########################################################################################################################
# INPUT
# str root                      root name for saving file
# list class Snap snapshots     list with information to be saved
# str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2                       identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1 (optional)         cutoff distance for first neighbor search
# float cut2 (optional)         cutoff distance for second neighbor search
#####
# OUTPUT
# list class Snap complex       list with all ion complexes found
########################################################################################################################
# def ion_find(root, snapshots, id1, id2, id3, cut1=3.0, cut2=1.4):
#     complex = []
#     # loop through different snapshots
#     for snap in snapshots:
#         # get complex information
#         comp = ion_single(snap, id1, id2, id3, cut1, cut2)
#         # append Snap object for data storage
#         complex.append(Snap(snap.iter, snap.time, snap.cell, None, None, dataframe=comp))
#     # save information to file
#     ion_save(root, complex, id1, id2, id3, cut1, cut2)
#     return complex


########################################################################################################################
# ROUTINE TO FIND ION COMPLEXES FOR MULTIPLE SNAPSHOTS
# PARALLEL VERSION OF ion_find() WITH PROGRESS BAR IN CONSOLE
########################################################################################################################
# INPUT
# str root                      root name for saving file
# list class Snap snapshots     list with information to be saved
# str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2                       identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1 (optional)         cutoff distance for first neighbor search
# float cut2 (optional)         cutoff distance for second neighbor search
#####
# OUTPUT
# list class Snap ion_comp      list of ion complexes found
########################################################################################################################
def ion_find_parallel(root, snapshots, id1, id2, id3, cut1, cut2):
    """
    Find ion complexes for multiple snapshots of atomic configurations.

    Args:
        root (str): root name of the files
        snapshots (list[:class:`.Snap`]): list of snapshots containing the atomic information
        id1 (str): identifier for atom used as center (e.g. 'MN')
        id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')
        id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')
        cut1 (float): cutoff distance for first neighbor search
        cut2 (float): cutoff distance for second neighbor search

    Returns:
        list[:class:`.Snap`]: list of snapshots containing an ion complex

    Parallelization based on :py:mod:`multiprocessing`.

    Note:
        Only one atom of type :data:`id1` allowed to be in a snapshot at the moment.
    """
    print("ION COMPLEX DETECTION IN PROGRESS")
    # set other arguments (necessary for parallel computing)
    multi_one = partial(ion_single, id1=id1, id2=id2, id3=id3, cut1=cut1, cut2=cut2)
    # run data extraction
    ion_comp = progress.parallel_progbar(multi_one, snapshots)
    # create output file
    ion_save(root, ion_comp, id1, id2, id3, cut1, cut2)
    print("ION COMPLEX DETECTION FINISHED")
    return ion_comp

"""
paw_structure.ion
-----------------
Ion complex detection using geometric :ref:`algorithm<Control_ION_algorithm>`.

Main routine is :func:`.ion_find_parallel`.

Dependencies:
    :py:mod:`functools`
    :py:mod:`miniutils`
    :py:mod:`numpy`
    :py:mod:`pandas`
    :mod:`.neighbor`
    :mod:`.utility`
    :class:`.Snap`

.. autosummary::

      ion_find_parallel
      ion_load
      ion_save
      ion_single
"""

import numpy as np
import pandas as pd
from functools import partial
import miniutils.progress_bar as progress
# MODULES WITHIN PROJECT
from . import neighbor
from . import utility
from .tra import Snap



########################################################################################################################
# FIND ION COMPLEX FOR A SINGLE SNAPSHOT
########################################################################################################################
# INPUT
# class Snap snap   snapshot containing all information
# str id1           identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2           identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3           identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1        cutoff distance for first neighbor search
# float cut2        cutoff distance for second neighbor search
#####
# OUTPUT
# pandas DataFrame  contains the whole complex centered around id1
########################################################################################################################
def ion_single(snap, id1, id2, id3, cut1, cut2):
    """
    Find ion complex of a single snapshot of atomic positions.

    Args:
        snap (:class:`.Snap`): single snapshot containing the atomic information
        id1 (str): identifier for atom used as center (e.g. 'MN')
        id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')
        id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')
        cut1 (float): cutoff distance for first neighbor search
        cut2 (float): cutoff distance for second neighbor search

    Returns:
        :class:`.Snap`: snapshot containing an ion complex

    Todo:
        Implement possibility for more atoms of type id1 or allow selection by name.
    """
    # check if only one atom is selected as ion
    if len(snap.atoms[snap.atoms['id'] == id1]) != 1:
        utility.err('ion_single', 0, [len(snap.atoms[snap.atoms['id'] == id1])])
    # check if all three are different species
    if id1 == id2 or id2 == id3 or id1 == id3:
        utility.err('ion_single', 1, [id1, id2, id3])
    # search first neighbors
    next1 = neighbor.neighbor_name(snap, id1, id2, cut1)
    # extract name lists
    id1_list = [atom[0] for atom in next1]
    id2_list = [y for x in [atom[1:] for atom in next1] for y in x]
    # search second neighbors
    next2 = neighbor.neighbor_name(snap, id2, id3, cut2, names=id2_list)
    # extract name list
    id3_list = [y for x in [atom[1:] for atom in next2] for y in x]
    # extract correct atom information
    id1_list = snap.atoms.loc[snap.atoms['name'].isin(id1_list)]
    id2_list = snap.atoms.loc[snap.atoms['name'].isin(id2_list)]
    id3_list = snap.atoms.loc[snap.atoms['name'].isin(id3_list)]
    comp = pd.concat([id1_list, id2_list, id3_list])
    return Snap(snap.iter, snap.time, snap.cell, None, None, dataframe=comp)


########################################################################################################################
# SAVE INFORMATION FROM ion_find TO FILE <root>.ext FOR LATER ANALYSIS
# TODO: check if snapshots is empty
########################################################################################################################
# INPUT
# str root                      root name for saving file
# list class Snap snapshots     list with information to be saved
# str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2                       identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1                    cutoff distance for first neighbor search
# float cut2                    cutoff distance for second neighbor search
# str ext (optional)            extension for the saved file: name = root + ext
########################################################################################################################
def ion_save(root, snapshots, id1, id2, id3, cut1, cut2, ext='.ion'):
    """
    Save results to file :ref:`Output_ion`.

    Args:
        root (str): root name for saving file
        snapshots (list[:class:`.Snap`]): list of snapshots containing an ion complex
        id1 (str): identifier for atom used as center (e.g. 'MN')
        id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')
        id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')
        cut1 (float): cutoff distance for first neighbor search
        cut2 (float): cutoff distance for second neighbor search
        ext (str, optional): default ".ion" - extension for the saved file: name = root + ext

    Todo:
        Check if snapshots is empty.
    """
    # open file
    path = root + ext
    try:
        f = open(path, 'w')
    except IOError:
        utility.err_file('ion_save', path)
    # write header
    f.write(utility.write_header())
    f.write("ION COMPLEXES\n")
    f.write("%-14s%14.8f\n" % ("T1", snapshots[0].time))
    f.write("%-14s%14.8f\n" % ("T2", snapshots[-1].time))
    f.write("%-14s%14d\n" % ("SNAPSHOTS", len(snapshots)))
    f.write("%-14s%14s\n" % ("ID1", id1))
    f.write("%-14s%14s\n" % ("ID2", id2))
    f.write("%-14s%14s\n" % ("ID3", id3))
    f.write("%-14s%14.8f\n" % ("CUT1", cut1))
    f.write("%-14s%14.8f\n" % ("CUT2", cut2))
    f.write("%-14s\n" % ("UNIT CELL"))
    np.savetxt(f, snapshots[0].cell, fmt="%14.8f")
    # write structure information
    for i in range(len(snapshots)):
        f.write("-" * 84 + "\n")
        f.write("%-14s%-14.8f%-14s%-14d%-14s%-14d\n" %
                ("TIME", snapshots[i].time, "ITERATION", snapshots[i].iter, "ATOMS", len(snapshots[i].atoms)))
        f.write("%-14s%-14s%-14s%14s%14s%14s\n" % ('NAME', 'ID', 'INDEX', 'X', 'Y', 'Z'))
        np.savetxt(f, snapshots[i].atoms, fmt="%-14s%-14s%-14d%14.8f%14.8f%14.8f")
    f.close()
    return


########################################################################################################################
# LOAD INFORMATION PREVIOUSLY SAVED BY ion_save()
# WARNING: READING IS LINE SENSITIVE! ONLY USE ON UNCHANGED FILES WRITTEN BY ion_save()
########################################################################################################################
# INPUT
# str root                      root name for the file to be loaded
# str ext (optional)            extension for the file to be loaded: name = root + ext
#####
# OUTPUT
# list class Snap snapshots     list of all information
########################################################################################################################
def ion_load(root, ext='.ion'):
    """
    Load information from the :ref:`Output_ion` file previously created by :func:`.ion_save`.

    Args:
        root (str): root name for the file to be loaded
        ext (str, optional): default ".ion" - extension for the file to be loaded: name = root + ext

    Returns:
        list[:class:`.Snap`]: list of snapshots containing an ion complex

    Note:
        Reading is line sensitive. Do not alter the output file before loading.
    """
    path = root + ext
    try:
        f = open(path, 'r')
    except IOError:
        utility.err_file('ion_load', path)
    text = f.readlines()  # read text as lines
    for i in range(len(text)):
        text[i] = text[i].split()  # split each line into list with strings as elements
    snapshots = []  # storage list
    for i in range(len(text)):
        if len(text[i]) > 1:
            if text[i][0] == 'UNIT':
                cell = np.array(text[i+1:i+4], dtype=float) # get unit cell
            if text[i][0] == "TIME":  # search for trigger of new snapshot
                iter = int(text[i][3])
                time = float(text[i][1])
                n_atoms = int(text[i][5])
                test = np.array(text[i + 2:i + 2 + n_atoms])
                atoms = {}
                atoms['name'] = test[:, 0]
                atoms['id'] = test[:, 1]
                atoms['index'] = np.array(test[:, 2], dtype=int)
                df = pd.DataFrame(data=atoms)
                # save information as class Snap
                snapshots.append(Snap(iter, time, cell, np.array(test[:, 3:6], dtype=np.float64), df))
    return snapshots


########################################################################################################################
# FIND ION COMPLEXES IN MULTIPLE SNAPSHOTS
# WARNING: NOT IN USE BECAUSE NO PARALLEL COMPUTING
########################################################################################################################
# INPUT
# str root                      root name for saving file
# list class Snap snapshots     list with information to be saved
# str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2                       identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1 (optional)         cutoff distance for first neighbor search
# float cut2 (optional)         cutoff distance for second neighbor search
#####
# OUTPUT
# list class Snap complex       list with all ion complexes found
########################################################################################################################
# def ion_find(root, snapshots, id1, id2, id3, cut1=3.0, cut2=1.4):
#     complex = []
#     # loop through different snapshots
#     for snap in snapshots:
#         # get complex information
#         comp = ion_single(snap, id1, id2, id3, cut1, cut2)
#         # append Snap object for data storage
#         complex.append(Snap(snap.iter, snap.time, snap.cell, None, None, dataframe=comp))
#     # save information to file
#     ion_save(root, complex, id1, id2, id3, cut1, cut2)
#     return complex


########################################################################################################################
# ROUTINE TO FIND ION COMPLEXES FOR MULTIPLE SNAPSHOTS
# PARALLEL VERSION OF ion_find() WITH PROGRESS BAR IN CONSOLE
########################################################################################################################
# INPUT
# str root                      root name for saving file
# list class Snap snapshots     list with information to be saved
# str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap
# str id2                       identifier for atoms as possible first neighbors (e.g. 'O_')
# str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_')
# float cut1 (optional)         cutoff distance for first neighbor search
# float cut2 (optional)         cutoff distance for second neighbor search
#####
# OUTPUT
# list class Snap ion_comp      list of ion complexes found
########################################################################################################################
def ion_find_parallel(root, snapshots, id1, id2, id3, cut1, cut2):
    """
    Find ion complexes for multiple snapshots of atomic configurations.

    Args:
        root (str): root name of the files
        snapshots (list[:class:`.Snap`]): list of snapshots containing the atomic information
        id1 (str): identifier for atom used as center (e.g. 'MN')
        id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')
        id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')
        cut1 (float): cutoff distance for first neighbor search
        cut2 (float): cutoff distance for second neighbor search

    Returns:
        list[:class:`.Snap`]: list of snapshots containing an ion complex

    Parallelization based on :py:mod:`multiprocessing`.

    Note:
        Only one atom of type :data:`id1` allowed to be in a snapshot at the moment.
    """
    print("ION COMPLEX DETECTION IN PROGRESS")
    # set other arguments (necessary for parallel computing)
    multi_one = partial(ion_single, id1=id1, id2=id2, id3=id3, cut1=cut1, cut2=cut2)
    # run data extraction
    ion_comp = progress.parallel_progbar(multi_one, snapshots)
    # create output file
    ion_save(root, ion_comp, id1, id2, id3, cut1, cut2)
    print("ION COMPLEX DETECTION FINISHED")
    return ion_comp

paw_structure.ion ----------------- Ion complex detection using geometric :ref:`algorithm<Control_ION_algorithm>`.  Main routine is :func:`.ion_find_parallel`.  Dependencies:     :py:mod:`functools`     :py:mod:`miniutils`     :py:mod:`numpy`     :py:mod:`pandas`     :mod:`.neighbor`     :mod:`.utility`     :class:`.Snap`  .. autosummary::        ion_find_parallel       ion_load       ion_save       ion_single # MODULES WITHIN PROJECT ######################################################################################################################## # FIND ION COMPLEX FOR A SINGLE SNAPSHOT ######################################################################################################################## # INPUT # class Snap snap   snapshot containing all information # str id1           identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap # str id2           identifier for atoms as possible first neighbors (e.g. 'O_') # str id3           identifier for atoms as possible neighbors of first neighbors (e.g. 'H_') # float cut1        cutoff distance for first neighbor search # float cut2        cutoff distance for second neighbor search ##### # OUTPUT # pandas DataFrame  contains the whole complex centered around id1 ######################################################################################################################## Find ion complex of a single snapshot of atomic positions.      Args:         snap (:class:`.Snap`): single snapshot containing the atomic information         id1 (str): identifier for atom used as center (e.g. 'MN')         id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')         id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')         cut1 (float): cutoff distance for first neighbor search         cut2 (float): cutoff distance for second neighbor search      Returns:         :class:`.Snap`: snapshot containing an ion complex      Todo:         Implement possibility for more atoms of type id1 or allow selection by name. # check if only one atom is selected as ion # check if all three are different species # search first neighbors # extract name lists # search second neighbors # extract name list # extract correct atom information ######################################################################################################################## # SAVE INFORMATION FROM ion_find TO FILE <root>.ext FOR LATER ANALYSIS # TODO: check if snapshots is empty ######################################################################################################################## # INPUT # str root                      root name for saving file # list class Snap snapshots     list with information to be saved # str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap # str id2                       identifier for atoms as possible first neighbors (e.g. 'O_') # str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_') # float cut1                    cutoff distance for first neighbor search # float cut2                    cutoff distance for second neighbor search # str ext (optional)            extension for the saved file: name = root + ext ######################################################################################################################## Save results to file :ref:`Output_ion`.      Args:         root (str): root name for saving file         snapshots (list[:class:`.Snap`]): list of snapshots containing an ion complex         id1 (str): identifier for atom used as center (e.g. 'MN')         id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')         id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')         cut1 (float): cutoff distance for first neighbor search         cut2 (float): cutoff distance for second neighbor search         ext (str, optional): default ".ion" - extension for the saved file: name = root + ext      Todo:         Check if snapshots is empty. # open file # write header # write structure information ######################################################################################################################## # LOAD INFORMATION PREVIOUSLY SAVED BY ion_save() # WARNING: READING IS LINE SENSITIVE! ONLY USE ON UNCHANGED FILES WRITTEN BY ion_save() ######################################################################################################################## # INPUT # str root                      root name for the file to be loaded # str ext (optional)            extension for the file to be loaded: name = root + ext ##### # OUTPUT # list class Snap snapshots     list of all information ######################################################################################################################## Load information from the :ref:`Output_ion` file previously created by :func:`.ion_save`.      Args:         root (str): root name for the file to be loaded         ext (str, optional): default ".ion" - extension for the file to be loaded: name = root + ext      Returns:         list[:class:`.Snap`]: list of snapshots containing an ion complex      Note:         Reading is line sensitive. Do not alter the output file before loading. # read text as lines # split each line into list with strings as elements # storage list # get unit cell # search for trigger of new snapshot # save information as class Snap ######################################################################################################################## # FIND ION COMPLEXES IN MULTIPLE SNAPSHOTS # WARNING: NOT IN USE BECAUSE NO PARALLEL COMPUTING ######################################################################################################################## # INPUT # str root                      root name for saving file # list class Snap snapshots     list with information to be saved # str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap # str id2                       identifier for atoms as possible first neighbors (e.g. 'O_') # str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_') # float cut1 (optional)         cutoff distance for first neighbor search # float cut2 (optional)         cutoff distance for second neighbor search ##### # OUTPUT # list class Snap complex       list with all ion complexes found ######################################################################################################################## # def ion_find(root, snapshots, id1, id2, id3, cut1=3.0, cut2=1.4): #     complex = [] #     # loop through different snapshots #     for snap in snapshots: #         # get complex information #         comp = ion_single(snap, id1, id2, id3, cut1, cut2) #         # append Snap object for data storage #         complex.append(Snap(snap.iter, snap.time, snap.cell, None, None, dataframe=comp)) #     # save information to file #     ion_save(root, complex, id1, id2, id3, cut1, cut2) #     return complex ######################################################################################################################## # ROUTINE TO FIND ION COMPLEXES FOR MULTIPLE SNAPSHOTS # PARALLEL VERSION OF ion_find() WITH PROGRESS BAR IN CONSOLE ######################################################################################################################## # INPUT # str root                      root name for saving file # list class Snap snapshots     list with information to be saved # str id1                       identifier for atom used as center (e.g. 'MN'); only one allowed to be in snap # str id2                       identifier for atoms as possible first neighbors (e.g. 'O_') # str id3                       identifier for atoms as possible neighbors of first neighbors (e.g. 'H_') # float cut1 (optional)         cutoff distance for first neighbor search # float cut2 (optional)         cutoff distance for second neighbor search ##### # OUTPUT # list class Snap ion_comp      list of ion complexes found ######################################################################################################################## Find ion complexes for multiple snapshots of atomic configurations.      Args:         root (str): root name of the files         snapshots (list[:class:`.Snap`]): list of snapshots containing the atomic information         id1 (str): identifier for atom used as center (e.g. 'MN')         id2 (str): identifier for atoms as possible first neighbors (e.g. 'O\_')         id3 (str): identifier for atoms as possible neighbors of first neighbors (e.g. 'H\_')         cut1 (float): cutoff distance for first neighbor search         cut2 (float): cutoff distance for second neighbor search      Returns:         list[:class:`.Snap`]: list of snapshots containing an ion complex      Parallelization based on :py:mod:`multiprocessing`.      Note:         Only one atom of type :data:`id1` allowed to be in a snapshot at the moment. # set other arguments (necessary for parallel computing) # run data extraction # create output file