
     i                         d Z ddlZddlZddlZddlZddlmZ ddlmZm	Z	 ddl
ZddlmZ ddlmZ ddlmZ d	d
lmZmZmZmZ d	dlmZmZ  ej        e          Z G d de          Z G d de          Zd Z d Z!dS )uV  Analysis building blocks --- :mod:`MDAnalysis.analysis.base`
============================================================

MDAnalysis provides building blocks for creating analysis classes. One can
think of each analysis class as a "tool" that performs a specific analysis over
the trajectory frames and stores the results in the tool.

Analysis classes are derived from :class:`AnalysisBase` by subclassing. This
inheritance provides a common workflow and API for users and makes many
additional features automatically available (such as frame selections and a
verbose progressbar). The important points for analysis classes are:

#. Analysis tools are Python classes derived from :class:`AnalysisBase`.
#. When instantiating an analysis, the :class:`Universe` or :class:`AtomGroup`
   that the analysis operates on is provided together with any other parameters
   that are kept fixed for the specific analysis.
#. The analysis is performed with :meth:`~AnalysisBase.run` method. It has a
   common set of arguments such as being able to select the frames the analysis
   is performed on. The `verbose` keyword argument enables additional output. A
   progressbar is shown by default that also shows an estimate for the
   remaining time until the end of the analysis.
#. Results are always stored in the attribute :attr:`AnalysisBase.results`,
   which is an instance of :class:`Results`, a kind of dictionary that allows
   allows item access via attributes. Each analysis class decides what and how
   to store in :class:`Results` and needs to document it. For time series, the
   :attr:`AnalysisBase.times` contains the time stamps of the analyzed frames.


Example of using a standard analysis tool
-----------------------------------------

For example, the :class:`MDAnalysis.analysis.rms.RMSD` performs a
root-mean-square distance analysis in the following way:

.. code-block:: python

   import MDAnalysis as mda
   from MDAnalysisTests.datafiles import TPR, XTC

   from MDAnalysis.analysis import rms

   u = mda.Universe(TPR, XTC)

   # (2) instantiate analysis
   rmsd = rms.RMSD(u, select='name CA')

   # (3) the run() method can select frames in different ways
   # run on all frames (with progressbar)
   rmsd.run(verbose=True)

   # or start, stop, and step can be used
   rmsd.run(start=2, stop=8, step=2)

   # a list of frames to run the analysis on can be passed
   rmsd.run(frames=[0,2,3,6,9])

   # a list of booleans the same length of the trajectory can be used
   rmsd.run(frames=[True, False, True, True, False, False, True, False,
                    False, True])

   # (4) analyze the results, e.g., plot
   t = rmsd.times
   y = rmsd.results.rmsd[:, 2]   # RMSD at column index 2, see docs

   import matplotlib.pyplot as plt
   plt.plot(t, y)
   plt.xlabel("time (ps)")
   plt.ylabel("RMSD (Å)")


Writing new analysis tools
--------------------------

In order to write new analysis tools, derive a class from :class:`AnalysisBase`
and define at least the :meth:`_single_frame` method, as described in
:class:`AnalysisBase`.

.. SeeAlso::

   The chapter `Writing your own trajectory analysis`_ in the *User Guide*
   contains a step-by-step example for writing analysis tools with
   :class:`AnalysisBase`.

.. _`Writing your own trajectory analysis`:
   https://userguide.mdanalysis.org/stable/examples/analysis/custom_trajectory_analysis.html


If your analysis is operating independently on each frame, you might consider
making it **parallelizable** via adding a :meth:`get_supported_backends` method,
and appropriate aggregation function for each of its results. For example, if
you have your :meth:`_single_frame` method storing important values under
:attr:`self.results.timeseries`, you will write:

.. code-block:: python

    class MyAnalysis(AnalysisBase):
        _analysis_algorithm_is_parallelizable = True

        @classmethod
        def get_supported_backends(cls):
            return ('serial', 'multiprocessing', 'dask',)

        
        def _get_aggregator(self):
          return ResultsGroup(lookup={'timeseries': ResultsGroup.ndarray_vstack})

See :mod:`MDAnalysis.analysis.results` for more on aggregating results.

.. SeeAlso::

   :ref:`parallel-analysis`



Classes
-------

The :class:`MDAnalysis.results.Results` and :class:`AnalysisBase` classes
are the essential building blocks for almost all MDAnalysis tools in the
:mod:`MDAnalysis.analysis` module. They aim to be easily useable and
extendable.

:class:`AnalysisFromFunction` and the :func:`analysis_class` functions are
simple wrappers that make it even easier to create fully-featured analysis
tools if only the single-frame analysis function needs to be written.

    N)partial)IterableUnion   )coordinates)	AtomGroup)ProgressBar   )BackendDaskBackendMultiprocessingBackendSerialBackendBase)ResultsResultsGroupc                      e Zd ZdZed             ZdZed             Zd dZ		 d!de
eej        f         fdZd	e
eej        f         fd
Z	 d!dZd Zd Zd Z	 d"dddej        dedd fdZ	 	 	 	 d!dedededede
eej        f         deej                 fdZ	 d de
eef         dededefdZ	 	 	 	 	 	 	 	 d#ddddededededededede
eef         defdZdefdZdS )$AnalysisBasea  Base class for defining multi-frame analysis

    The class is designed as a template for creating multi-frame analyses.
    This class will automatically take care of setting up the trajectory
    reader for iterating, and it offers to show a progress meter.
    Computed results are stored inside the :attr:`results` attribute.

    To define a new Analysis, :class:`AnalysisBase` needs to be subclassed
    and :meth:`_single_frame` must be defined. It is also possible to define
    :meth:`_prepare` and :meth:`_conclude` for pre- and post-processing.
    All results should be stored as attributes of the
    :class:`MDAnalysis.analysis.results.Results` container.

    .. Note::
       The instance attributes are created during and on conclusion of
       calling the :meth:`AnalysisBase.run` method. Accessing an attribute
       before it has been created will raise an :exc:`AttributeError`.


    Parameters
    ----------
    trajectory : MDAnalysis.coordinates.base.ReaderBase
        A trajectory Reader
    verbose : bool, optional
        Turn on more logging and debugging

    Attributes
    ----------
    times: numpy.ndarray
        Array of times of the Timesteps that were analyzed.
        Only exists after calling :meth:`AnalysisBase.run`.
    frames: numpy.ndarray
        Array of frame indices that were analyzed.
        Only exists after calling :meth:`AnalysisBase.run`.
    results: :class:`Results`
        Results of calculation are stored here, after call
        to :meth:`AnalysisBase.run`.
    n_frames: int
        number of *analyzed* frames, i.e., after taking into account
        the `start`, `stop`, and `step` values from
        :meth:`AnalysisBase.run`.
        Only exists after calling :meth:`AnalysisBase.run`.
    start: int
        Frame index of the first trajectory frame that was analyzed.
        Only exists after calling :meth:`AnalysisBase.run`.
    stop: int
        Frame index of the last trajectory frame that was analyzed.
        Only exists after calling :meth:`AnalysisBase.run`.
    step: int
        Every `step` frame was analyzed, as ``trajectory[start:stop:step]``.
        Only exists after calling :meth:`AnalysisBase.run`.


    Example
    -------
    .. code-block:: python

       from MDAnalysis.analysis.base import AnalysisBase

       class NewAnalysis(AnalysisBase):
           def __init__(self, atomgroup, parameter, **kwargs):
               super(NewAnalysis, self).__init__(atomgroup.universe.trajectory,
                                                 **kwargs)
               self._parameter = parameter
               self._ag = atomgroup

           def _prepare(self):
               # OPTIONAL
               # Called before iteration on the trajectory has begun.
               # Data structures can be set up at this time
               self.results.example_result = []

           def _single_frame(self):
               # REQUIRED
               # Called after the trajectory is moved onto each new frame.
               # store an example_result of `some_function` for a single frame
               self.results.example_result.append(some_function(self._ag,
                                                                self._parameter))

           def _conclude(self):
               # OPTIONAL
               # Called once iteration on the trajectory is finished.
               # Apply normalisation and averaging to results here.
               self.results.example_result = np.asarray(self.example_result)
               self.results.example_result /=  np.sum(self.result)

    Afterwards the new analysis can be run like this

    .. code-block:: python

       import MDAnalysis as mda
       from MDAnalysisTests.datafiles import PSF, DCD

       u = mda.Universe(PSF, DCD)

       na = NewAnalysis(u.select_atoms('name CA'), 35)
       na.run(start=10, stop=20)
       print(na.results.example_result)
       # results can also be accessed by key
       print(na.results["example_result"])


    .. versionchanged:: 1.0.0
        Support for setting `start`, `stop`, and `step` has been removed. These
        should now be directly passed to :meth:`AnalysisBase.run`.

    .. versionchanged:: 2.0.0
        Added :attr:`results`

    .. versionchanged:: 2.8.0
        Added ability to run analysis in parallel using either a
        built-in backend (`multiprocessing` or `dask`) or a custom
        `backends.BackendBase` instance with an implemented `apply` method
        that is used to run the computations.
    c                     dS )a  Tuple with backends supported by the core library for a given class.
        User can pass either one of these values as ``backend=...`` to
        :meth:`run()` method, or a custom object that has ``apply`` method
        (see documentation for :meth:`run()`):

         - 'serial': no parallelization
         - 'multiprocessing': parallelization using `multiprocessing.Pool`
         - 'dask': parallelization using `dask.delayed.compute()`. Requires
           installation of `mdanalysis[dask]`

        If you want to add your own backend to an existing class, pass a
        :class:`backends.BackendBase` subclass (see its documentation to learn
        how to implement it properly), and specify ``unsupported_backend=True``.

        Returns
        -------
        tuple
            names of built-in backends that can be used in :meth:`run(backend=...)`


        .. versionadded:: 2.8.0
        )serial clss    b/srv/www/vhosts/g4struct/public_html/venv/lib/python3.11/site-packages/MDAnalysis/analysis/base.pyget_supported_backendsz#AnalysisBase.get_supported_backends"  s	    0 {    Fc                     | j         S )a  Boolean mark showing that a given class can be parallelizable with
        split-apply-combine procedure. Namely, if we can safely distribute
        :meth:`_single_frame` to multiple workers and then combine them with a
        proper :meth:`_conclude` call. If set to ``False``, no backends except
        for ``serial`` are supported.

        .. note::   If you want to check parallelizability of the whole class, without
                    explicitly creating an instance of the class, see
                    :attr:`_analysis_algorithm_is_parallelizable`. Note that you
                    setting it to other value will break things if the algorithm
                    behind the analysis is not trivially parallelizable.


        Returns
        -------
        bool
            if a given ``AnalysisBase`` subclass instance
            is parallelizable with split-apply-combine, or not


        .. versionadded:: 2.8.0
        )%_analysis_algorithm_is_parallelizableselfs    r   parallelizablezAnalysisBase.parallelizableB  s    0 99r   c                 H    || _         || _        t                      | _        d S N)_trajectory_verboser   results)r   
trajectoryverbosekwargss       r   __init__zAnalysisBase.__init__\  s     %yyr   Nreturnc                     || _         |.t          d |||fD                       st          d          |}n,|                    |||          \  }}}t	          |||          }|||c| _        | _        | _        |S )a   Defines limits for the whole run, as passed by self.run() arguments

        Parameters
        ----------
        trajectory : mda.Reader
            a trajectory Reader
        start : int, optional
            start frame of analysis, by default None
        stop : int, optional
            stop frame of analysis, by default None
        step : int, optional
            number of frames to skip between each analysed frame, by default None
        frames : array_like, optional
            array of integers or booleans to slice trajectory; cannot be
            combined with ``start``, ``stop``, ``step``; by default None

        Returns
        -------
        Union[slice, np.ndarray]
            Appropriate slicer for the trajectory that would give correct iteraction
            order via trajectory[slicer]

        Raises
        ------
        ValueError
            if *both* `frames` and at least one of ``start``, ``stop``, or ``step``
            is provided (i.e. set to not ``None`` value).


        .. versionadded:: 2.8.0
        Nc              3      K   | ]}|d u V  	d S r!   r   .0opts     r   	<genexpr>z2AnalysisBase._define_run_frames.<locals>.<genexpr>  s&      BBssd{BBBBBBr   .start/stop/step cannot be combined with frames)r"   all
ValueErrorcheck_slice_indicesslicestartstopstepr   r%   r5   r6   r7   framesslicers          r   _define_run_frameszAnalysisBase._define_run_framesa  s    D &BBudD.ABBBBB  D   FF * > >tT! !E4 5$--F+0$(
DItyr   r:   c                     | j         |         | _        t          | j                  | _        t	          j        | j        t                    | _        t	          j        | j                  | _        dS )ax  Prepares sliced trajectory for use in subsequent parallel computations:
        namely, assigns self._sliced_trajectory and its appropriate attributes,
        self.n_frames, self.frames and self.times.

        Parameters
        ----------
        slicer : Union[slice, np.ndarray]
            appropriate slicer for the trajectory


        .. versionadded:: 2.8.0
        dtypeN)	r"   _sliced_trajectorylenn_framesnpzerosintr9   times)r   r:   s     r   _prepare_sliced_trajectoryz'AnalysisBase._prepare_sliced_trajectory  sU     #'"26":D344ht}C888Xdm,,


r   c                 b    |                      |||||          }|                     |           dS )ag  Pass a Reader object and define the desired iteration pattern
        through the trajectory

        Parameters
        ----------
        trajectory : mda.Reader
            A trajectory Reader
        start : int, optional
            start frame of analysis
        stop : int, optional
            stop frame of analysis
        step : int, optional
            number of frames to skip between each analysed frame
        frames : array_like, optional
            array of integers or booleans to slice trajectory; cannot be
            combined with ``start``, ``stop``, ``step``

            .. versionadded:: 2.2.0

        Raises
        ------
        ValueError
            if *both* `frames` and at least one of ``start``, ``stop``, or
            ``frames`` is provided (i.e., set to another value than ``None``)


        .. versionchanged:: 1.0.0
            Added .frames and .times arrays as attributes

        .. versionchanged:: 2.2.0
            Added ability to iterate through trajectory by passing a list of
            frame indices in the `frames` keyword argument

        .. versionchanged:: 2.8.0
            Split function into two: :meth:`_define_run_frames` and
            :meth:`_prepare_sliced_trajectory`: first one defines the limits
            for the whole run and is executed once during :meth:`run` in
            :meth:`_setup_frames`, second one prepares sliced trajectory for
            each of the workers and gets executed twice: one time in
            :meth:`_setup_frames` for the whole trajectory, second time in
            :meth:`_compute` for each of the computation groups.
        N)r;   rF   r8   s          r   _setup_frameszAnalysisBase._setup_frames  s:    Z ((UD$OO''/////r   c                      t          d          )a  Calculate data from a single frame of trajectory

        Don't worry about normalising, just deal with a single frame.
        Attributes accessible during your calculations:

          - ``self._frame_index``: index of the frame in results array
          - ``self._ts`` -- Timestep instance
          - ``self._sliced_trajectory`` -- trajectory that you're iterating over
          - ``self.results`` -- :class:`MDAnalysis.analysis.results.Results` instance
            holding run results initialized in :meth:`_prepare`.
        z!Only implemented in child classes)NotImplementedErrorr   s    r   _single_framezAnalysisBase._single_frame  s     ""EFFFr   c                     dS )a  
        Set things up before the analysis loop begins.

        Notes
        -----
        ``self.results`` is initialized already in :meth:`self.__init__` with an
        empty instance of :class:`MDAnalysis.analysis.results.Results` object.
        You can still call your attributes as if they were usual ones,
        ``Results`` just keeps track of that to be able to run a proper
        aggregation after a parallel run, if necessary.
        Nr   r   s    r   _preparezAnalysisBase._prepare  s	     	r   c                     dS )a  Finalize the results you've gathered.

        Called at the end of the :meth:`run` method to finish everything up.

        Notes
        -----
        Aggregation of results from individual workers happens in
        :meth:`self.run()`, so here you have to implement everything as if you
        had a non-parallel run. If you want to enable proper aggregation for
        parallel runs for you analysis class, implement ``self._get_aggregator``
        and check :mod:`MDAnalysis.analysis.results` for how to use it.
        Nr   r   s    r   	_concludezAnalysisBase._conclude  s	     	r   )progressbar_kwargsindexed_framesr&   c                8   |i }t                               d           |t          | dd          n|}|dddf         }t                               d           |                     |           |                                  t          |          dk    r| S t          t          | j        fd	|i|          D ]E\  }}|| _	        || _
        |j        | j        |<   |j        | j        |<   |                                  Ft                               d
           | S )a  Perform the calculation on a balanced slice of frames
        that have been setup prior to that using _setup_computation_groups()

        Parameters
        ----------
        indexed_frames : np.ndarray
            np.ndarray of (n, 2) shape, where first column is frame iteration
            indices and second is frame numbers

        verbose : bool, optional
            Turn on verbosity

        progressbar_kwargs : dict, optional
            ProgressBar keywords with custom parameters regarding progress bar
            position, etc; see :class:`MDAnalysis.lib.log.ProgressBar`
            for full list.


        .. versionadded:: 2.8.0
        NzChoosing frames to analyzer#   Fr
   zStarting preparation)r:   r   r&   zFinishing up)loggerinfogetattrrF   rM   r@   	enumerater	   r?   _frame_index_tsframer9   timerE   rK   )r   rQ   r&   rP   r9   idxtss          r   _computezAnalysisBase._compute  s@   6 %!#0111 18GD*e,,,W 	  1%*+++''v'666v;;!K ' 18<N 
 
 		! 		!GC
 !$DDH!xDK gDJsO    N###r   n_partsr5   r6   r7   r9   c                    |7| j                             |||          \  }}}t          j        |||          }n-t	          d |||fD                       st          d          |}t	          d |D                       r)t          j        t          |                    }||         }t          j        t          j        t          |                    |g          j        }t          |          dk    r!t          j	        dt          j
                  gS t          |          |k     r't          |          }t          j        d| d	           t          j        ||          S )
a  
        Splits the trajectory frames, defined by ``start/stop/step`` or
        ``frames``, into ``n_parts`` even groups, preserving their indices.

        Parameters
        ----------
        n_parts : int
            number of parts to split the workload into
        start : int, optional
            start frame
        stop : int, optional
            stop frame
        step : int, optional
            step size for analysis (1 means to read every frame)
        frames : array_like, optional
            array of integers or booleans to slice trajectory; ``frames`` can
            only be used *instead* of ``start``, ``stop``, and ``step``. Setting
            *both* ``frames`` and at least one of ``start``, ``stop``, ``step``
            to a non-default value will raise a :exc:`ValueError`.

        Raises
        ------
        ValueError
            if *both* ``frames`` and at least one of ``start``, ``stop``, or
            ``frames`` is provided (i.e., set to another value than ``None``)

        Returns
        -------
        computation_groups : list[np.ndarray]
            list of (n, 2) shaped np.ndarrays with frame indices and numbers


        .. versionadded:: 2.8.0
        Nc              3      K   | ]}|d u V  	d S r!   r   r,   s     r   r/   z9AnalysisBase._setup_computation_groups.<locals>.<genexpr>f  s&      @@SSD[@@@@@@r   r0   c              3   @   K   | ]}t          |t                    V  d S r!   )
isinstanceboolr-   objs     r   r/   z9AnalysisBase._setup_computation_groups.<locals>.<genexpr>k  s,      <<z#t$$<<<<<<r   r   )r   r   r=   zSet `n_parts` to z3 to match the total number of frames being analyzed)r"   r3   rB   aranger1   r2   r@   vstackTemptyint64warningswarnarray_split)	r   r^   r5   r6   r7   r9   used_framesrf   enumerated_framess	            r   _setup_computation_groupsz&AnalysisBase._setup_computation_groups7  s   T > $ 0 D DtT! !E4 )E466KK@@UD$,?@@@@@ 	!MNNN K<<<<<<< 	.Ys;//00F -K IYs;''((+6
 

 	  !!Q&&HV2844455"##g--+,,GM2G 2 2 2  
 ~/999r   backend	n_workersunsupported_backendc                    t           t          t          d                    ||          }fd|                                 D             }| j        s |t           urt          d| j                   |s%| j        r||vrt          d|d| j                   |t           ur2t          d | j	        j
        D                       rt          d          t          |t                    r ||          S t          |t                    r8|6t          |d
          r&|j        |k    rt          d|j        d|d          t          |t                    rt          |d          st          d|d          |S )a  Matches a passed backend string value with class attributes
        :attr:`parallelizable` and :meth:`get_supported_backends()`
        to check if downstream calculations can be performed.

        Parameters
        ----------
        backend : Union[str, BackendBase]
            backend to be used:
               - ``str`` is matched to a builtin backend (one of "serial",
                 "multiprocessing" and "dask")
               - ``BackendBase`` subclass is checked for the presence of
                 an :meth:`apply` method
        n_workers : int
            positive integer with number of workers (processes, in case of
            built-in backends) to split the work between
        unsupported_backend : bool, optional
            if you want to run your custom backend on a parallelizable class
            that has not been tested by developers, by default ``False``

        Returns
        -------
        BackendBase
            instance of a ``BackendBase`` class that will be used for computations

        Raises
        ------
        ValueError
            if :attr:`parallelizable` is set to ``False`` but backend is
            not ``serial``
        ValueError
            if :attr:`parallelizable` is ``True`` and custom backend instance is used
            without specifying ``unsupported_backend=True``
        ValueError
            if your trajectory has associated parallelizable transformations
            but backend is not serial
        ValueError
            if ``n_workers`` was specified twice -- in the run() method and durin
            ``__init__`` of a custom backend
        ValueError
            if your backend object instance doesn't have an ``apply`` method


        .. versionadded:: 2.8.0
        r   multiprocessingdaskc                 :    g | ]}                     |          S r   )get)r-   bbuiltin_backendss     r   
<listcomp>z3AnalysisBase._configure_backend.<locals>.<listcomp>  s5     %
 %
 %
()  ##%
 %
 %
r   zCan not parallelize class zQMust specify 'unsupported_backend=True'if you want to use a custom backend_class=z for c              3   &   K   | ]}|j          V  d S r!   )r   )r-   ts     r   r/   z2AnalysisBase._configure_backend.<locals>.<genexpr>  s9       6
 6
%&  6
 6
 6
 6
 6
 6
r   zFTrajectory should not have associated unparallelizable transformations)rr   Nrr   z0n_workers specified twice: in backend.n_workers=zand in run(n_workers=z). Remove it from run()applyzbackend=zc is invalid: should have 'apply' method and be instance of MDAnalysis.analysis.backends.BackendBase)r   r   r   ry   r   r   r2   	__class__anyr"   transformationsrb   strr   hasattrrr   )r   rq   rr   rs   backend_classsupported_backend_classesr{   s         @r   _configure_backendzAnalysisBase._configure_backend  sJ   f $5
 
 ),,Wg>>%
 %
 %
 %
-1-H-H-J-J%
 %
 %
!
 " 	L}M'I'IJ$.JJKKK $
	#
	 %>>>Y3@Y YHLY Y   --# 6
 6
*.*:*J6
 6
 6
 3
 3
- B   gs## 	6 =95555 w,,	%-- &!Y..FW5F F F"+F F F   ';// 	wW8
 8
 	 Rw R R R   r   )rs   rP   c	                ~   |dn|}|
i n|
}
|
s|r*|dk    s$t          |t                    st          d          |.t          |t                    rt	          |d          r|j        nd}||n|}|                     |||	          }t	          |d          r*||j        k     rt          j        d|j        d|           t          | j
        |
|	          }|                     | j        ||||
           |                     |||||          }|                    ||          }t          j        d |D                       | _        t          j        d |D                       | _        d |D             }|                                 }|                    |          | _        |                                  | S )a	  Perform the calculation

        Parameters
        ----------
        start : int, optional
            start frame of analysis
        stop : int, optional
            stop frame of analysis
        step : int, optional
            number of frames to skip between each analysed frame
        frames : array_like, optional
            array of integers or booleans to slice trajectory; ``frames`` can
            only be used *instead* of ``start``, ``stop``, and ``step``. Setting
            *both* ``frames`` and at least one of ``start``, ``stop``, ``step``
            to a non-default value will raise a :exc:`ValueError`.

            .. versionadded:: 2.2.0
        verbose : bool, optional
            Turn on verbosity

        progressbar_kwargs : dict, optional
            ProgressBar keywords with custom parameters regarding progress bar
            position, etc; see :class:`MDAnalysis.lib.log.ProgressBar`
            for full list. Available only for ``backend='serial'``
        backend : Union[str, BackendBase], optional
            By default, performs calculations in a serial fashion.
            Otherwise, user can choose a backend: ``str`` is matched to a
            builtin backend (one of ``serial``, ``multiprocessing`` and
            ``dask``), or a :class:`MDAnalysis.analysis.results.BackendBase`
            subclass.

            .. versionadded:: 2.8.0
        n_workers : int
            positive integer with number of workers (processes, in case of
            built-in backends) to split the work between

            .. versionadded:: 2.8.0
        n_parts : int, optional
            number of parts to split computations across. Can be more than
            number of workers.

            .. versionadded:: 2.8.0
        unsupported_backend : bool, optional
            if you want to run your custom backend on a parallelizable class
            that has not been tested by developers, by default False

            .. versionadded:: 2.8.0


        .. versionchanged:: 2.2.0
            Added ability to analyze arbitrary frames by passing a list of
            frame indices in the `frames` keyword argument.

        .. versionchanged:: 2.5.0
            Add `progressbar_kwargs` parameter,
            allowing to modify description, position etc of tqdm progressbars

        .. versionchanged:: 2.8.0
            Introduced ``backend``, ``n_workers``, ``n_parts`` and
            ``unsupported_backend`` keywords, and refactored the method logic to
            support parallelizable execution.
        Nr   z3Can not display progressbar with non-serial backendrr   r
   )rq   rr   rs   z;Analysis not making use of all workers: executor.n_workers=z is greater than n_parts=)rP   r&   )r%   r5   r6   r7   r9   )r5   r6   r7   r9   r^   c                     g | ]	}|j         
S r   )r9   rd   s     r   r|   z$AnalysisBase.run.<locals>.<listcomp>  s     F F F F F Fr   c                     g | ]	}|j         
S r   )rE   rd   s     r   r|   z$AnalysisBase.run.<locals>.<listcomp>  s    DDDc	DDDr   c                     g | ]	}|j         
S r   )r$   rd   s     r   r|   z$AnalysisBase.run.<locals>.<listcomp>  s    @@@##+@@@r   )rb   r   r2   r   r   rr   r   rk   rl   r   r]   rH   r"   rp   r   rB   hstackr9   rE   _get_aggregatormerger$   rO   )r   r5   r6   r7   r9   r&   rr   r^   rq   rs   rP   executorworker_funccomputation_groupsremote_objectsremote_resultsresults_aggregators                    r   runzAnalysisBase.run  s`   Z &o((7 %,BB2D 	  	' 	x:g}#E#EE    g{33G[11!! 	   '))G ** 3 + 
 
 Hk**	/69K/K/KMH)H H=DH H   M1
 
 

 	' 	 	
 	
 	
 ";;dfg < 
 
 08~~+0
 0
 i F F~ F F FGGYDD^DDDEE
 A@@@@!1133)//??r   c                 "    t          d          S )zReturns a default aggregator that takes entire results
        if there is a single object, and raises ValueError otherwise

        Returns
        -------
        ResultsGroup
            aggregating object


        .. versionadded:: 2.8.0
        N)lookup)r   r   s    r   r   zAnalysisBase._get_aggregator  s     4((((r   )F)NNNNr!   )NNNNNNNN) __name__
__module____qualname____doc__classmethodr   r   propertyr   r(   r   r4   rB   ndarrayr;   rF   rH   rK   rM   rO   rc   r]   rD   listrp   r   r   r   r   r   r   r   r   r   r   r   r      s       r rh   [: -2): : X:2! ! ! ! DH/ /	ubj 	!/ / / /b-ubj7H1I - - - -& DH.0 .0 .0 .0`G G G    $ 6
  6 6 6
6 6 
6 6 6 6v +/F: F:F: F: 	F:
 F: eRZ'(F: 
bj	F: F: F: F:X %*	v vsK'(v v "	v
 
v v v vt +/W %*W W WW W 	W
 W W W W sK'(W "W W W Wr) ) ) ) ) ) )r   r   c                   V     e Zd ZdZdZed             Zd
 fd	Zd Zd Z	d Z
d	 Z xZS )AnalysisFromFunctiona  Create an :class:`AnalysisBase` from a function working on AtomGroups

    Parameters
    ----------
    function : callable
        function to evaluate at each frame
    trajectory : MDAnalysis.coordinates.Reader, optional
        trajectory to iterate over. If ``None`` the first AtomGroup found in
        args and kwargs is used as a source for the trajectory.
    *args : list
        arguments for `function`
    **kwargs : dict
        arguments for `function` and :class:`AnalysisBase`

    Attributes
    ----------
    results.frames : numpy.ndarray
            simulation frames used in analysis
    results.times : numpy.ndarray
            simulation times used in analysis
    results.timeseries : numpy.ndarray
            Results for each frame of the wrapped function,
            stored after call to :meth:`AnalysisFromFunction.run`.

    Raises
    ------
    ValueError
        if `function` has the same `kwargs` as :class:`AnalysisBase`

    Example
    -------
    .. code-block:: python

        def rotation_matrix(mobile, ref):
            return mda.analysis.align.rotation_matrix(mobile, ref)[0]

        rot = AnalysisFromFunction(rotation_matrix, trajectory,
                                    mobile, ref).run()
        print(rot.results.timeseries)


    .. versionchanged:: 1.0.0
        Support for directly passing the `start`, `stop`, and `step` arguments
        has been removed. These should instead be passed to
        :meth:`AnalysisFromFunction.run`.

    .. versionchanged:: 2.0.0
        Former :attr:`results` are now stored as :attr:`results.timeseries`

    .. versionchanged:: 2.8.0
        Added :meth:`get_supported_backends()`, introducing 'serial', 'multiprocessing'
        and 'dask' backends.
    Tc                     dS )Nru   r   r   s    r   r   z+AnalysisFromFunction.get_supported_backends  s    44r   Nc                    |'t          |t          j        j                  s|f|z   }d }|Mt	          j        ||                                          D ]%}t          |t                    r|j        j	        } n&|t          d          || _        || _        || _        t          t          |                               |           d S )NzCouldn't find a trajectory)rb   r   baseProtoReader	itertoolschainvaluesr   universer%   r2   functionargsr'   superr   r(   )r   r   r%   r   r'   argr   s         r   r(   zAnalysisFromFunction.__init__  s    ":{'7'CDD # =4'DJ tV]]__==  c9-- !$!8JE 9::: 	"D))22:>>>>>r   c                     g | j         _        d S r!   )r$   
timeseriesr   s    r   rM   zAnalysisFromFunction._prepare  s    "$r   c                 8    t          dt           j        i          S )Nr   )r   flatten_sequencer   s    r   r   z$AnalysisFromFunction._get_aggregator  s    \<+HIJJJr   c                 n    | j         j                             | j        | j        i | j                   d S r!   )r$   r   appendr   r   r'   r   s    r   rK   z"AnalysisFromFunction._single_frame  sA    &&DM49444	
 	
 	
 	
 	
r   c                     | j         | j        _         | j        | j        _        t          j        | j        j                  | j        _        d S r!   )r9   r$   rE   rB   asarrayr   r   s    r   rO   zAnalysisFromFunction._conclude  s9    "k!Z"$*T\-D"E"Er   r!   )r   r   r   r   r   r   r   r(   rM   r   rK   rO   __classcell__)r   s   @r   r   r     s        4 4l -1)5 5 [5? ? ? ? ? ?0% % %K K K
 
 

F F F F F F Fr   r   c                 6      G  fddt                     S )aP  Transform a function operating on a single frame to an
    :class:`AnalysisBase` class.

    Parameters
    ----------
    function : callable
        function to evaluate at each frame

    Attributes
    ----------
    results.frames : numpy.ndarray
            simulation frames used in analysis
    results.times : numpy.ndarray
            simulation times used in analysis
    results.timeseries : numpy.ndarray
            Results for each frame of the wrapped function,
            stored after call to :meth:`AnalysisFromFunction.run`.

    Raises
    ------
    ValueError
        if `function` has the same `kwargs` as :class:`AnalysisBase`

    Examples
    --------

    For use in a library, we recommend the following style

    .. code-block:: python

        def rotation_matrix(mobile, ref):
            return mda.analysis.align.rotation_matrix(mobile, ref)[0]
        RotationMatrix = analysis_class(rotation_matrix)

    It can also be used as a decorator

    .. code-block:: python

        @analysis_class
        def RotationMatrix(mobile, ref):
            return mda.analysis.align.rotation_matrix(mobile, ref)[0]

        rot = RotationMatrix(u.trajectory, mobile, ref).run(step=2)
        print(rot.results.timeseries)


    .. versionchanged:: 2.0.0
        Former :attr:`results` are now stored as :attr:`results.timeseries`
    c                   <     e Zd Zd fd	Zed             Z xZS )$analysis_class.<locals>.WrapperClassNc                 H     t          |           j        |g|R i | d S r!   )r   r(   )r   r%   r   r'   WrapperClassr   r   s       r   r(   z-analysis_class.<locals>.WrapperClass.__init__9  sM    .E,%%.*'+  /5    r   c                     dS )N)r   rw   r   r   s    r   r   z;analysis_class.<locals>.WrapperClass.get_supported_backends>  s    %%r   r!   )r   r   r   r(   r   r   r   )r   r   r   s   @r   r   r   8  sc        	 	 	 	 	 	 	 	
 
	& 	& 
	& 	& 	& 	& 	&r   r   )r   )r   r   s   `@r   analysis_classr     sG    f& & & & & & & &+ & & & r   c                    	 t          j        t          j                  }n.# t          $ r! t          j        t          j                  }Y nw xY wt          |j                  }d t          |j	        | d         |j                  D             }	 t          j        |           }n$# t          $ r t          j        |           }Y nw xY w|
                                D ]@}||j	        v r5t          d                    ||
                                                    Ai }|                                D ]\  }}	|                    ||	          ||<   ||fS )a  
    Create two dictionaries with `kwargs` separated for `function` and
    :class:`AnalysisBase`

    Parameters
    ----------
    function : callable
        function to be called
    kwargs : dict
        keyword argument dictionary

    Returns
    -------
    base_args : dict
        dictionary of AnalysisBase kwargs
    kwargs : dict
        kwargs without AnalysisBase kwargs

    Raises
    ------
    ValueError
        if `function` has the same `kwargs` as :class:`AnalysisBase`

    c                     i | ]\  }}||	S r   r   )r-   namevals      r   
<dictcomp>z/_filter_baseanalysis_kwargs.<locals>.<dictcomp>f  s.       D# 	c  r   NzIargument name '{}' clashes with AnalysisBase argument.Now allowed are: {})inspectgetfullargspecr   r(   AttributeError
getargspecr@   defaultszipr   keysr2   formatitemspop)
r   r'   base_argspecn_base_defaultsbase_kwargsargspecbase_kw	base_argsargnamedefaults
             r   _filter_baseanalysis_kwargsr   E  s   2A-l.CDD A A A),*?@@A ,/00O .//0,2G
 
  K/(22 / / /$X../ ##%%  gl""&&,fWk6F6F6H6H&I&I   # I'--// : :#ZZ99	'fs!   ! (AAB& &CC)"r   r   r   loggingrk   	functoolsr   typingr   r   numpyrB    r   core.groupsr   lib.logr	   backendsr   r   r   r   r$   r   r   	getLoggerr   rS   objectr   r   r   r   r   r   r   <module>r      s  .~ ~~              " " " " " " " "           # # # # # # ! ! ! ! ! !            + * * * * * * *		8	$	$o) o) o) o) o)6 o) o) o)dcF cF cF cF cF< cF cF cFL= = =@: : : : :r   