a
    ߙfbN                    @   sl  d 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	m
Z
 ddlmZmZ ddlmZ ddlZddlmZ dd	lmZ d
dlmZ g dZdd Zdd ZG dd dZG dd dedZG dd deZG dd deZG dd deZG dd deZ G dd deZ!G d d! d!eZ"G d"d# d#eZ#d$d% Z$i Z%d&e%e < d'e%e< d(e%e< d)e%e!< d*e%e"< dS )+aJ  
This module contains a general framework for defining graphs of transformations
between coordinates, suitable for either spatial coordinates or more generalized
coordinate systems.

The fundamental idea is that each class is a node in the transformation graph,
and transitions from one node to another are defined as functions (or methods)
wrapped in transformation objects.

This module also includes more specific transformation classes for
celestial/spatial coordinate frames, generally focused around matrix-style
transformations that are typically how the algorithms are defined.
    N)warn)ABCMetaabstractmethod)defaultdict)suppresscontextmanager)	signature)units)AstropyWarning   )matrix_product)	TransformGraphCoordinateTransformFunctionTransformBaseAffineTransformAffineTransformStaticMatrixTransformDynamicMatrixTransform%FunctionTransformWithFiniteDifferenceCompositeTransformc                 C   s   i }| D ]}| |j q|S )z
    A `dict` of all the attributes of all frame classes in this
    `TransformGraph`.

    Broken out of the class so this can be called on a temporary frame set to
    validate new additions to the transform graph before actually adding them.
    )updateframe_attributes)	frame_setresult	frame_cls r   Blib/python3.9/site-packages/astropy/coordinates/transformations.pyframe_attrs_from_set)   s    r   c                 C   s@   t  }| D ]0}|j}| D ]}|D ]}||jg q$qq
|S )a	  
    A `set` of all component names every defined within any frame class in
    this `TransformGraph`.

    Broken out of the class so this can be called on a temporary frame set to
    validate new additions to the transform graph before actually adding them.
    )setZ#_frame_specific_representation_infovaluesr   Z	framename)r   r   r   Zrep_infoZmappingsZrep_mapr   r   r   frame_comps_from_set9   s    r    c                   @   s   e Zd ZdZdd Zedd Zedd Zedd	 Zed
d Z	dd Z
dd Zdd Zdd Zdd Zdd Zdd Zdg ddddfddZdd  Zd)d"d#Zd!d$d%d&Zed'd( ZdS )*r   zC
    A graph representing the paths between coordinate frames.
    c                 C   s   t t| _|   d S N)r   dict_graphinvalidate_cacheselfr   r   r   __init__Q   s    
zTransformGraph.__init__c                 C   s\   | j d u rVi  | _ }| jD ]:}t|dd }|d urt|tsB|g}|D ]}|||< qFq| j S )Nname)_cached_names_dctr   getattr
isinstancelist)r&   ZdctcZnmr(   r   r   r   _cached_namesU   s    



zTransformGraph._cached_namesc                 C   sP   | j du rFt | _ | jD ],}| j | | j| D ]}| j | q2q| j  S )zT
        A `set` of all the frame classes present in this `TransformGraph`.
        N)_cached_frame_setr   r#   addcopy)r&   abr   r   r   r   c   s    

zTransformGraph.frame_setc                 C   s   | j du rt| j| _ | j S )zg
        A `dict` of all the attributes of all frame classes in this
        `TransformGraph`.
        N)_cached_frame_attributesr   r   r%   r   r   r   r   q   s    
zTransformGraph.frame_attributesc                 C   s   | j du rt| j| _ | j S )zw
        A `set` of all component names every defined within any frame class in
        this `TransformGraph`.
        N)_cached_component_namesr    r   r%   r   r   r   frame_component_names|   s    
z$TransformGraph.frame_component_namesc                 C   s(   d| _ d| _d| _d| _i | _i | _dS )a  
        Invalidates the cache that stores optimizations for traversing the
        transform graph.  This is called automatically when transforms
        are added or removed, but will need to be called manually if
        weights on transforms are modified inplace.
        N)r)   r/   r4   r5   _shortestpaths_composite_cacher%   r   r   r   r$      s    zTransformGraph.invalidate_cachec           
      C   s   t |stdt |s$tdt|s4td| j }|| || tt|	 }t
|}||}|rt }|D ]0}	|	|jv r||g |	|jv r||g qtdt|||| j| |< |   dS )a  
        Add a new coordinate transformation to the graph.

        Parameters
        ----------
        fromsys : class
            The coordinate frame class to start from.
        tosys : class
            The coordinate frame class to transform into.
        transform : `CoordinateTransform`
            The transformation object. Typically a `CoordinateTransform` object,
            although it may be some other callable that is called with the same
            signature.

        Raises
        ------
        TypeError
            If ``fromsys`` or ``tosys`` are not classes or ``transform`` is
            not callable.
        fromsys must be a classtosys must be a classztransform must be callablezFrame(s) {} contain invalid attribute names: {}
Frame attributes can not conflict with *any* of the frame data component names (see `frame_transform_graph.frame_component_names`).N)inspectisclass	TypeErrorcallabler   r1   r0   r   r   keysr    intersectionr   r   
ValueErrorformatr,   r#   r$   )
r&   fromsystosys	transformr   attrscompsZinvalid_attrsZinvalid_framesattrr   r   r   add_transform   s0    







zTransformGraph.add_transformc                 C   s  |du s|du r|du r |du s(t d|du r8t d| jD ]:}| j| }|D ]}|| |u rP||= |} qpqP|r> qq>t d| dnZ|du r| j| |d n>| j| |d}||u r| j| | nt d|||| j| i kr| j| |   dS )a  
        Removes a coordinate transform from the graph.

        Parameters
        ----------
        fromsys : class or None
            The coordinate frame *class* to start from. If `None`,
            ``transform`` will be searched for and removed (``tosys`` must
            also be `None`).
        tosys : class or None
            The coordinate frame *class* to transform into. If `None`,
            ``transform`` will be searched for and removed (``fromsys`` must
            also be `None`).
        transform : callable or None
            The transformation object to be removed or `None`.  If `None`
            and ``tosys`` and ``fromsys`` are supplied, there will be no
            check to ensure the correct object is removed.
        Nz1fromsys and tosys must both be None if either arez)cannot give all Nones to remove_transformzCould not find transform z in the graphz)Current transform from {} to {} is not {})rA   r#   popgetrB   r$   )r&   rC   rD   rE   r2   agraphr3   Zcurrr   r   r   remove_transform   s4    

zTransformGraph.remove_transformc                    s  t d| u r(|| j  vr(|gdfS || j  v rd| j  | } |gt t|dr\|jndfS  | jv r| j  }||v r|| S dfS g }| jD ]8}||vr|| | j| D ]}||vr|| qq |vs||vrdfS i }| jD ]L}i  ||< }	| j| }
|
D ],}t t|
| dr4|
| jnd|	|< qq fddt|D }|ddd g g i }t|dkrt	
|\}}}}|krd|f||< |D ]\}}}}d|f||< qqn||f||< || ||vrqt|| D ]}||vrtt|D ]}|| d	 |kr q>qtd
||| |  }||| d k r||| d< t||| d< t	| qqt|| j < || S )a2  
        Computes the shortest distance along the transform graph from
        one system to another.

        Parameters
        ----------
        fromsys : class
            The coordinate frame class to start from.
        tosys : class
            The coordinate frame class to transform into.

        Returns
        -------
        path : list of class or None
            The path from ``fromsys`` to ``tosys`` as an in-order sequence
            of classes.  This list includes *both* ``fromsys`` and
            ``tosys``. Is `None` if there is no possible path.
        distance : float or int
            The total distance/priority from ``fromsys`` to ``tosys``.  If
            priorities are not set this is the number of transforms
            needed. Is ``inf`` if there is no possible path.
        infr   priorityr   Nc                    s$   g | ]\}}| ur||g gqS r   r   ).0inrC   rN   r   r   
<listcomp>S      z5TransformGraph.find_shortest_path.<locals>.<listcomp>   z+n2 not in heap - this should be impossible!   )floatr#   hasattrrO   r7   append	enumerateinsertlenheapqheappoprangerA   r,   heapify)r&   rC   rD   tZfpathsnodesr2   r3   ZedgeweightsZaewrL   qr   dZorderirR   pathZn2rQ   Znewdr   rS   r   find_shortest_path  sl    
 





,





z!TransformGraph.find_shortest_pathc           
      C   s   t |stdt |s$td| ||\}}|du r@dS g }|}|dd D ]}|| j| |  |}qT||f}|| jvrt|||dd}	|	| j|< | j| S )a  
        Generates and returns the `CompositeTransform` for a transformation
        between two coordinate systems.

        Parameters
        ----------
        fromsys : class
            The coordinate frame class to start from.
        tosys : class
            The coordinate frame class to transform into.

        Returns
        -------
        trans : `CompositeTransform` or None
            If there is a path from ``fromsys`` to ``tosys``, this is a
            transform object for that path.   If no path could be found, this is
            `None`.

        Notes
        -----
        This function always returns a `CompositeTransform`, because
        `CompositeTransform` is slightly more adaptable in the way it can be
        called than other transform classes. Specifically, it takes care of
        intermediate steps of transformations in a way that is consistent with
        1-hop transformations.

        zfromsys is not a classztosys is not a classNr   F)register_graph)r;   r<   r=   rh   r[   r#   r8   r   )
r&   rC   rD   rg   Zdistance
transformsZcurrsyspZfttupleZ	comptransr   r   r   get_transform~  s&    



zTransformGraph.get_transformc                 C   s   | j |dS )aa  
        Tries to locate the coordinate class with the provided alias.

        Parameters
        ----------
        name : str
            The alias to look up.

        Returns
        -------
        `BaseCoordinateFrame` subclass
            The coordinate class corresponding to the ``name`` or `None` if
            no such class exists.
        N)r.   rK   )r&   r(   r   r   r   lookup_name  s    zTransformGraph.lookup_namec                 C   s   t | j S )z
        Returns all available transform names. They will all be
        valid arguments to `lookup_name`.

        Returns
        -------
        nms : list
            The aliases for coordinate systems.
        )r,   r.   r?   r%   r   r   r   	get_names  s    
zTransformGraph.get_namesTNplainc           "         s  g } j D ]8}||vr ||  j | D ]}	|	|vr*||	 q*q
|D ]}
|
|vrH||
 qHg }t fdd jD }|D ]@}||v rd|| }|d|j| q||jd  qg } j D ]^} j | }|D ]J}	||	 }t|dr|jnd}|rt	|j
 nd}||j|	j||f qqd	g}|d
 |d|d  |D ]\\}}}}d}|rxd| d}nd}d| d}|||}|| d| | d qT|d |d |d d|}|dur|dkr&t|d}|| W d   n1 s0    Y  n|g}|durD|d|  tj|tjtjtjd}||\} }!|jdkrtd|! t|d}||  W d   n1 s0    Y  |S )a  
        Converts this transform graph to the graphviz_ DOT format.

        Optionally saves it (requires `graphviz`_ be installed and on your path).

        .. _graphviz: http://www.graphviz.org/

        Parameters
        ----------
        priorities : bool
            If `True`, show the priority values for each transform.  Otherwise,
            the will not be included in the graph.
        addnodes : sequence of str
            Additional coordinate systems to add (this can include systems
            already in the transform graph, but they will only appear once).
        savefn : None or str
            The file name to save this graph to or `None` to not save
            to a file.
        savelayout : str
            The graphviz program to use to layout the graph (see
            graphviz_ for details) or 'plain' to just save the DOT graph
            content. Ignored if ``savefn`` is `None`.
        saveformat : str
            The graphviz output format. (e.g. the ``-Txxx`` option for
            the command line program - see graphviz docs for details).
            Ignored if ``savefn`` is `None`.
        color_edges : bool
            Color the edges between two nodes (frames) based on the type of
            transform. ``FunctionTransform``: red, ``StaticMatrixTransform``:
            blue, ``DynamicMatrixTransform``: green.

        Returns
        -------
        dotgraph : str
            A string with the DOT format graph.
        c                    s(   g | ]    fd dj  D fqS )c                    s   g | ]\}}| kr|qS r   r   rP   kvfr   r   rT     rU   z:TransformGraph.to_dot_graph.<locals>.<listcomp>.<listcomp>)r.   items)rP   r%   rs   r   rT     s   z/TransformGraph.to_dot_graph.<locals>.<listcomp>z`\n`z#{0} [shape=oval label="{0}\n`{1}`"]z[ shape=oval ]rO   r   Zblackz)digraph AstropyCoordinateTransformGraph {zgraph [rankdir=LR]z; ;z[ {0} {1} ]z	label = "" z	color = "z -> zoverlap=false}
Nro   wz-T)stdinstdoutstderrr   zproblem running graphviz: 
)r#   r[   r"   r   joinrB   __name__rZ   rO   trans_to_color	__class__openwrite
subprocessPopenPIPEZcommunicate
returncodeOSError)"r&   Z
prioritiesZaddnodesZsavefnZ
savelayoutZ
saveformatZcolor_edgesrd   r2   r3   ZnodeZ	nodenamesZinvclsaliasesrR   aliasesZ	edgenamesrL   rE   pricolorlinesZenm1Zenm2ZweightsZlabelstr_fmtZpriority_partZ
color_partZlabelstrZdotgraphrt   argsprocr}   r~   r   r%   r   to_dot_graph  sv    '










,

*zTransformGraph.to_dot_graphc           	      C   s   ddl }| }| jD ]8}||vr,|| | j| D ]}||vr6|| q6q| jD ]P}| j| }|D ]<}|| }t|dr|jnd}t|j }|j||||d qhqV|S )a  
        Converts this transform graph into a networkx graph.

        .. note::
            You must have the `networkx <https://networkx.github.io/>`_
            package installed for this to work.

        Returns
        -------
        nxgraph : ``networkx.Graph``
            This `TransformGraph` as a `networkx.Graph <https://networkx.github.io/documentation/stable/reference/classes/graph.html>`_.
        r   NrO   r   )Zweightr   )	ZnetworkxZGraphr#   Zadd_noderZ   rO   r   r   Zadd_edge)	r&   ZnxZnxgraphr2   r3   rL   rE   r   r   r   r   r   to_networkx_graph?  s     




z TransformGraph.to_networkx_graphr   c                    s    fdd}|S )a  
        A function decorator for defining transformations.

        .. note::
            If decorating a static method of a class, ``@staticmethod``
            should be  added *above* this decorator.

        Parameters
        ----------
        transcls : class
            The class of the transformation object to create.
        fromsys : class
            The coordinate frame class to start from.
        tosys : class
            The coordinate frame class to transform into.
        priority : float or int
            The priority if this transform when finding the shortest
            coordinate transform path - large numbers are lower priorities.

        Additional keyword arguments are passed into the ``transcls``
        constructor.

        Returns
        -------
        deco : function
            A function that can be called on another function as a decorator
            (see example).

        Notes
        -----
        This decorator assumes the first argument of the ``transcls``
        initializer accepts a callable, and that the second and third
        are ``fromsys`` and ``tosys``. If this is not true, you should just
        initialize the class manually and use `add_transform` instead of
        using this decorator.

        Examples
        --------
        ::

            graph = TransformGraph()

            class Frame1(BaseCoordinateFrame):
               ...

            class Frame2(BaseCoordinateFrame):
                ...

            @graph.transform(FunctionTransform, Frame1, Frame2)
            def f1_to_f2(f1_obj):
                ... do something with f1_obj ...
                return f2_obj

        c                    s   |  fd | S NrO   ri   r   )funcrC   kwargsrO   r&   rD   transclsr   r   deco  s    z&TransformGraph.transform.<locals>.decor   )r&   r   rC   rD   rO   r   r   r   r   r   rE   c  s    7zTransformGraph.transformrO   c          	   
      s   ||g|}|d }  ||} fddt|dd |dd D }d|v rXtdt|jdkrtd|j d|j d	 ||t||||d
  dS )a  
        Add a single-step transform that encapsulates a multi-step transformation path,
        using the transforms that already exist in the graph.

        The created transform internally calls the existing transforms.  If all of the
        transforms are affine, the merged transform is
        `~astropy.coordinates.transformations.DynamicMatrixTransform` (if there are no
        origin shifts) or `~astropy.coordinates.transformations.AffineTransform`
        (otherwise).  If at least one of the transforms is not affine, the merged
        transform is
        `~astropy.coordinates.transformations.FunctionTransformWithFiniteDifference`.

        This method is primarily useful for defining loopback transformations
        (i.e., where ``fromsys`` and the final ``tosys`` are the same).

        Parameters
        ----------
        fromsys : class
            The coordinate frame class to start from.
        tosys : class
            The coordinate frame class to transform to.
        *furthersys : class
            Additional coordinate frame classes to transform to in order.
        priority : number
            The priority of this transform when finding the shortest
            coordinate transform path - large numbers are lower priorities.

        Notes
        -----
        Even though the created transform is a single step in the graph, it
        will still internally call the constituent transforms.  Thus, there is
        no performance benefit for using this created transform.

        For Astropy's built-in frames, loopback transformations typically use
        `~astropy.coordinates.ICRS` to be safe.  Tranforming through an inertial
        frame ensures that changes in observation time and observer
        location/velocity are properly accounted for.

        An error will be raised if a direct transform between ``fromsys`` and
        ``tosys`` already exist.
        rV   c                    s   g | ]\}}  ||qS r   )rl   )rP   Zframe_aZframe_br%   r   r   rT     s   z8TransformGraph._add_merged_transform.<locals>.<listcomp>Nr   z(This transformation path is not possiblezA direct transform for z->z already existsr   )	rl   ziprA   r^   rj   r   rI   r   _as_single_transform)	r&   rC   rD   rO   Z
furthersysZframesZlastsys	full_pathrj   r   r%   r   _add_merged_transform  s    *
z$TransformGraph._add_merged_transformc              	   c   s   d}g }zj| j  D ]B}| D ]4}t||r ||t||f}|| t||| q qdV  W |D ]}t|  qdn|D ]}t|  qx0 dS )a  
        Context manager to impose a finite-difference time step on all applicable transformations

        For each transformation in this transformation graph that has the attribute
        ``finite_difference_dt``, that attribute is set to the provided value.  The only standard
        transformation with this attribute is
        `~astropy.coordinates.transformations.FunctionTransformWithFiniteDifference`.

        Parameters
        ----------
        dt : `~astropy.units.Quantity` ['time'] or callable
            If a quantity, this is the size of the differential used to do the finite difference.
            If a callable, should accept ``(fromcoord, toframe)`` and return the ``dt`` value.
        finite_difference_dtN)r#   r   rZ   r*   r[   setattr)r&   dtkeyZsaved_settingsZ	to_framesrE   Zold_settingZsettingr   r   r   impose_finite_difference_dt  s    

z*TransformGraph.impose_finite_difference_dt)r   )r   
__module____qualname____doc__r'   propertyr.   r   r   r6   r$   rI   rM   rh   rl   rm   rn   r   r   rE   r   r   r   r   r   r   r   r   L   s2   





:9v3
p$
@8r   c                   @   s6   e Zd ZdZdddZdd Zdd	 Zed
d ZdS )r   a0  
    An object that transforms a coordinate from one system to another.
    Subclasses must implement `__call__` with the provided signature.
    They should also call this superclass's ``__init__`` in their
    ``__init__``.

    Parameters
    ----------
    fromsys : `~astropy.coordinates.BaseCoordinateFrame` subclass
        The coordinate frame class to start from.
    tosys : `~astropy.coordinates.BaseCoordinateFrame` subclass
        The coordinate frame class to transform into.
    priority : float or int
        The priority if this transform when finding the shortest
        coordinate transform path - large numbers are lower priorities.
    register_graph : `TransformGraph` or None
        A graph to register this transformation with on creation, or
        `None` to leave it unregistered.
    r   Nc                 C   s   t |stdt |s$td|| _|| _t|| _|rJ| | nt |r^t |sftdg  | _}t	|drt	|dr|j
 D ]}||j
 v r|| qd S )Nr9   r:   z!fromsys and tosys must be classesget_frame_attr_names)r;   r<   r=   rC   rD   rY   rO   registerZoverlapping_frame_attr_namesrZ   r   r?   r[   )r&   rC   rD   rO   ri   ZoverlapZfrom_nmr   r   r   r'     s$    




zCoordinateTransform.__init__c                 C   s   | | j| j|  dS )a   
        Add this transformation to the requested Transformation graph,
        replacing anything already connecting these two coordinates.

        Parameters
        ----------
        graph : `TransformGraph` object
            The graph to register this transformation with.
        N)rI   rC   rD   r&   Zgraphr   r   r   r   ,  s    
zCoordinateTransform.registerc                 C   s   | | j| j|  dS )aY  
        Remove this transformation from the requested transformation
        graph.

        Parameters
        ----------
        graph : a TransformGraph object
            The graph to unregister this transformation from.

        Raises
        ------
        ValueError
            If this is not currently in the transform graph.
        N)rM   rC   rD   r   r   r   r   
unregister8  s    zCoordinateTransform.unregisterc                 C   s   dS )as  
        Does the actual coordinate transformation from the ``fromsys`` class to
        the ``tosys`` class.

        Parameters
        ----------
        fromcoord : `~astropy.coordinates.BaseCoordinateFrame` subclass instance
            An object of class matching ``fromsys`` that is to be transformed.
        toframe : object
            An object that has the attributes necessary to fully specify the
            frame.  That is, it must have attributes with names that match the
            keys of the dictionary that ``tosys.get_frame_attr_names()``
            returns. Typically this is of class ``tosys``, but it *might* be
            some other class as long as it has the appropriate attributes.

        Returns
        -------
        tocoord : `BaseCoordinateFrame` subclass instance
            The new coordinate after the transform has been applied.
        Nr   r&   	fromcoordtoframer   r   r   __call__I  s    zCoordinateTransform.__call__)r   N)	r   r   r   r   r'   r   r   r   r   r   r   r   r   r     s   
r   )	metaclassc                       s*   e Zd ZdZd fdd	Zdd Z  ZS )	r   a	  
    A coordinate transformation defined by a function that accepts a
    coordinate object and returns the transformed coordinate object.

    Parameters
    ----------
    func : callable
        The transformation function. Should have a call signature
        ``func(formcoord, toframe)``. Note that, unlike
        `CoordinateTransform.__call__`, ``toframe`` is assumed to be of type
        ``tosys`` for this function.
    fromsys : class
        The coordinate frame class to start from.
    tosys : class
        The coordinate frame class to transform into.
    priority : float or int
        The priority if this transform when finding the shortest
        coordinate transform path - large numbers are lower priorities.
    register_graph : `TransformGraph` or None
        A graph to register this transformation with on creation, or
        `None` to leave it unregistered.

    Raises
    ------
    TypeError
        If ``func`` is not callable.
    ValueError
        If ``func`` cannot accept two arguments.


    r   Nc                    s   t |stdttX t| dd  j D }t fdd|D dkrb j|vrbtdW d    n1 sv0    Y  || _	t
 j||||d d S )	Nzfunc must be callablec                 S   s   g | ]
}|j qS r   )kindrP   xr   r   r   rT     rU   z.FunctionTransform.__init__.<locals>.<listcomp>c                 3   s   | ]}| j kr|V  qd S r!   )ZPOSITIONAL_ONLYr   Zsigr   r   	<genexpr>  rU   z-FunctionTransform.__init__.<locals>.<genexpr>rW   z/provided function does not accept two argumentsr   )r>   r=   r   r   
parametersr   r^   ZVAR_POSITIONALrA   r   superr'   )r&   r   rC   rD   rO   ri   Zkindsr   r   r   r'     s    
&zFunctionTransform.__init__c                 C   sL   |  ||}t|| js.td| d| j |jjrH|jjsHtdt |S )Nz$the transformation function yielded z but should have been of type zApplied a FunctionTransform to a coordinate frame with differentials, but the FunctionTransform does not handle differentials, so they have been dropped.)r   r+   rD   r=   datadifferentialsr   r
   )r&   r   r   resr   r   r   r     s    
zFunctionTransform.__call__)r   N)r   r   r   r   r'   r   __classcell__r   r   r   r   r   a  s    r   c                       sT   e Zd ZdZddddej df fdd	Zedd	 Zej	d
d	 Zdd Z
  ZS )r   a  
    A coordinate transformation that works like a `FunctionTransform`, but
    computes velocity shifts based on the finite-difference relative to one of
    the frame attributes.  Note that the transform function should *not* change
    the differential at all in this case, as any differentials will be
    overridden.

    When a differential is in the from coordinate, the finite difference
    calculation has two components. The first part is simple the existing
    differential, but re-orientation (using finite-difference techniques) to
    point in the direction the velocity vector has in the *new* frame. The
    second component is the "induced" velocity.  That is, the velocity
    intrinsic to the frame itself, estimated by shifting the frame using the
    ``finite_difference_frameattr_name`` frame attribute a small amount
    (``finite_difference_dt``) in time and re-calculating the position.

    Parameters
    ----------
    finite_difference_frameattr_name : str or None
        The name of the frame attribute on the frames to use for the finite
        difference.  Both the to and the from frame will be checked for this
        attribute, but only one needs to have it. If None, no velocity
        component induced from the frame itself will be included - only the
        re-orientation of any existing differential.
    finite_difference_dt : `~astropy.units.Quantity` ['time'] or callable
        If a quantity, this is the size of the differential used to do the
        finite difference.  If a callable, should accept
        ``(fromcoord, toframe)`` and return the ``dt`` value.
    symmetric_finite_difference : bool
        If True, the finite difference is computed as
        :math:`\frac{x(t + \Delta t / 2) - x(t + \Delta t / 2)}{\Delta t}`, or
        if False, :math:`\frac{x(t + \Delta t) - x(t)}{\Delta t}`.  The latter
        case has slightly better performance (and more stable finite difference
        behavior).

    All other parameters are identical to the initializer for
    `FunctionTransform`.

    r   NZobstimeTc	           	         s*   t  ||||| || _|| _|| _d S r!   )r   r'    finite_difference_frameattr_namer   symmetric_finite_difference)	r&   r   rC   rD   rO   ri   r   r   r   r   r   r   r'     s    z.FunctionTransformWithFiniteDifference.__init__c                 C   s   | j S r!   )!_finite_difference_frameattr_namer%   r   r   r   r     s    zFFunctionTransformWithFiniteDifference.finite_difference_frameattr_namec                 C   sd   |d u rd | _ | _nD|| jjv }|| jjv }|s6|rD|| _ || _ntd|| j| j|| _d S )NFz<Frame attribute name {} is not a frame attribute of {} or {})_diff_attr_in_fromsys_diff_attr_in_tosysrC   r   rD   rA   rB   r   )r&   valueZdiff_attr_in_fromsysZdiff_attr_in_tosysr   r   r   r     s    c                 C   s  ddl m}m} | j}|jjrt| jr8| ||}n| j}|d }||j	 }|||}	|j
}
| jr|
j|
jd j|  }|||||}|
j|
jd j|  }|||||}n.|
j|
jd j|  }|||||}|	}|j
|j
 j| }| j}|d urt| jr| jrF|t||| i}|jf i |}n|}| jrv|t||| i}|jf i |}n|}|||}| jr|t||| i}|jf i |}n|}| jr|t||| i}|jf i |}n|}|||}nn| jr|t||| i}|jf i |}n|}| jrL|t||| i}|jf i |}n|}|||}|	}||j
|j
 j| 7 }||}|	j |}|	|S |||S d S )Nr   )CartesianRepresentationCartesianDifferentialrW   s)representationr   r   r   r   r   r>   r   realize_framewithout_differentialsZ	cartesianr   ZxyzZd_xyzr   r   r*   Z	replicater   Zreplicate_without_datato_cartesianwith_differentials)r&   r   r   r   r   Zsupcallr   ZhalfdtZfrom_difflessZreprwithoutdiffZfromcoord_cartZfwdxyzZfwdZbackxyzZbackZdiffxyzattrnameZkwsZfrom_diffless_fwdZ	fwd_frameZfrom_diffless_backZ
back_framenewdiffZreprwithdiffr   r   r   r     sz    






z.FunctionTransformWithFiniteDifference.__call__)r   r   r   r   usecondr'   r   r   setterr   r   r   r   r   r   r     s   (	

r   c                   @   s,   e Zd ZdZdd Zdd Zedd ZdS )	r   a]  Base class for common functionality between the ``AffineTransform``-type
    subclasses.

    This base class is needed because ``AffineTransform`` and the matrix
    transform classes share the ``__call__()`` method, but differ in how they
    generate the affine parameters.  ``StaticMatrixTransform`` passes in a
    matrix stored as a class attribute, and both of the matrix transforms pass
    in ``None`` for the offset. Hence, user subclasses would likely want to
    subclass this (rather than ``AffineTransform``) if they want to provide
    alternative transformations using this machinery.
    c                    sH  ddl m}m m}m}m} |jdjv }|d u r@|d u r@S |j|jf}	|o^t	jd |	}
|ort	jd |}t	|r|d urt
djnX|r|
s|r|d urd|jv rt
djd jn"tjdkrtdtj|r8t	|r8|
s8|s8jd jd j}d|in|rF  }t fddj D }||}|d ur||}|d ur| |  }n| }|r|s|jd }|d urd|jv r||jd  }|d|i}t	|j|r|r|
s|s|jd |jjd j}||tfd	djD }|jjd j|d
< d|jjd jf ddi|i}n8|r|
r|jd |jjd j|di}n|j}||jj}||}n<|r|
r|jjd j}||jj|j}||jj|}|rD|rD||jj}|d|jjd i}|S )Nr   )UnitSphericalRepresentationr   SphericalDifferentialSphericalCosLatDifferentialRadialDifferentialr   zPosition information stored on coordinate frame is insufficient to do a full-space position transformation (representation class: {})zVelocity information stored on coordinate frame is insufficient to do a full-space velocity transformation (differential class: {})zRepresentation passed to AffineTransform contains multiple associated differentials. Only a single differential with velocity units is presently supported (differentials: {}).c                    s    g | ]\}}||  fqS r   )represent_as)rP   rq   Zdiff)r   r   r   r   rT     s   z8BaseAffineTransform._apply_transform.<locals>.<listcomp>c                    s   g | ]}|t  |fqS r   r*   )rP   comp)r   r   r   rT     s   
d_distancer1   F)r   r   r   r   r   r   r   r   Z_unit_differentialr+   r=   rB   r   r^   rA   strr   r   r   r   r"   ru   rE   Z
componentsr   Z_dimensional_differential)r&   r   matrixoffsetr   r   r   r   Zhas_velocityZ_unit_diffsZunit_vel_diffZrad_vel_diffZ	unit_diffZrepZdiffsnewrepZveldiffZ	_unit_clsr   Zdiff_clsr   )r   r   r   r   _apply_transformG  s    













z$BaseAffineTransform._apply_transformc                 C   s(   |  ||}| j|g|R  }||S r!   )_affine_paramsr   r   )r&   r   r   paramsr   r   r   r   r     s    zBaseAffineTransform.__call__c                 C   s   d S r!   r   r   r   r   r   r     s    z"BaseAffineTransform._affine_paramsN)r   r   r   r   r   r   r   r   r   r   r   r   r   :  s    r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )	r   a  
    A coordinate transformation specified as a function that yields a 3 x 3
    cartesian transformation matrix and a tuple of displacement vectors.

    See `~astropy.coordinates.builtin_frames.galactocentric.Galactocentric` for
    an example.

    Parameters
    ----------
    transform_func : callable
        A callable that has the signature ``transform_func(fromcoord, toframe)``
        and returns: a (3, 3) matrix that operates on ``fromcoord`` in a
        Cartesian representation, and a ``CartesianRepresentation`` with
        (optionally) an attached velocity ``CartesianDifferential`` to represent
        a translation and offset in velocity to apply after the matrix
        operation.
    fromsys : class
        The coordinate frame class to start from.
    tosys : class
        The coordinate frame class to transform into.
    priority : float or int
        The priority if this transform when finding the shortest
        coordinate transform path - large numbers are lower priorities.
    register_graph : `TransformGraph` or None
        A graph to register this transformation with on creation, or
        `None` to leave it unregistered.

    Raises
    ------
    TypeError
        If ``transform_func`` is not callable

    r   Nc                    s.   t |std|| _t j||||d d S )Nztransform_func is not callabler   )r>   r=   transform_funcr   r'   )r&   r   rC   rD   rO   ri   r   r   r   r'     s    zAffineTransform.__init__c                 C   s   |  ||S r!   )r   r   r   r   r   r     s    zAffineTransform._affine_params)r   Nr   r   r   r   r'   r   r   r   r   r   r   r     s
   "  
r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )	r   a:  
    A coordinate transformation defined as a 3 x 3 cartesian
    transformation matrix.

    This is distinct from DynamicMatrixTransform in that this kind of matrix is
    independent of frame attributes.  That is, it depends *only* on the class of
    the frame.

    Parameters
    ----------
    matrix : array-like or callable
        A 3 x 3 matrix for transforming 3-vectors. In most cases will
        be unitary (although this is not strictly required). If a callable,
        will be called *with no arguments* to get the matrix.
    fromsys : class
        The coordinate frame class to start from.
    tosys : class
        The coordinate frame class to transform into.
    priority : float or int
        The priority if this transform when finding the shortest
        coordinate transform path - large numbers are lower priorities.
    register_graph : `TransformGraph` or None
        A graph to register this transformation with on creation, or
        `None` to leave it unregistered.

    Raises
    ------
    ValueError
        If the matrix is not 3 x 3

    r   Nc                    sF   t |r| }t|| _| jjdkr.tdt j||||d d S )N)rX   rX   zProvided matrix is not 3 x 3r   )r>   npZarrayr   shaperA   r   r'   )r&   r   rC   rD   rO   ri   r   r   r   r'   >  s    zStaticMatrixTransform.__init__c                 C   s
   | j d fS r!   )r   r   r   r   r   r   I  s    z$StaticMatrixTransform._affine_params)r   Nr   r   r   r   r   r     s    r   c                       s*   e Zd ZdZd fdd	Zdd Z  ZS )	r   a+  
    A coordinate transformation specified as a function that yields a
    3 x 3 cartesian transformation matrix.

    This is similar to, but distinct from StaticMatrixTransform, in that the
    matrix for this class might depend on frame attributes.

    Parameters
    ----------
    matrix_func : callable
        A callable that has the signature ``matrix_func(fromcoord, toframe)`` and
        returns a 3 x 3 matrix that converts ``fromcoord`` in a cartesian
        representation to the new coordinate system.
    fromsys : class
        The coordinate frame class to start from.
    tosys : class
        The coordinate frame class to transform into.
    priority : float or int
        The priority if this transform when finding the shortest
        coordinate transform path - large numbers are lower priorities.
    register_graph : `TransformGraph` or None
        A graph to register this transformation with on creation, or
        `None` to leave it unregistered.

    Raises
    ------
    TypeError
        If ``matrix_func`` is not callable

    r   Nc                    s.   t |std|| _t j||||d d S )Nzmatrix_func is not callabler   )r>   r=   matrix_funcr   r'   )r&   r   rC   rD   rO   ri   r   r   r   r'   m  s    zDynamicMatrixTransform.__init__c                 C   s   |  ||d fS r!   )r   r   r   r   r   r   v  s    z%DynamicMatrixTransform._affine_params)r   Nr   r   r   r   r   r   M  s
     	r   c                       s:   e Zd ZdZd fdd	Zdd Zd	d
 Zdd Z  ZS )r   a  
    A transformation constructed by combining together a series of single-step
    transformations.

    Note that the intermediate frame objects are constructed using any frame
    attributes in ``toframe`` or ``fromframe`` that overlap with the intermediate
    frame (``toframe`` favored over ``fromframe`` if there's a conflict).  Any frame
    attributes that are not present use the defaults.

    Parameters
    ----------
    transforms : sequence of `CoordinateTransform` object
        The sequence of transformations to apply.
    fromsys : class
        The coordinate frame class to start from.
    tosys : class
        The coordinate frame class to transform into.
    priority : float or int
        The priority if this transform when finding the shortest
        coordinate transform path - large numbers are lower priorities.
    register_graph : `TransformGraph` or None
        A graph to register this transformation with on creation, or
        `None` to leave it unregistered.
    collapse_static_mats : bool
        If `True`, consecutive `StaticMatrixTransform` will be collapsed into a
        single transformation to speed up the calculation.

    r   NTc                    s0   t  j||||d |r"| |}t|| _d S r   )r   r'   _combine_staticstuplerj   )r&   rj   rC   rD   rO   ri   Zcollapse_static_matsr   r   r   r'     s    
zCompositeTransform.__init__c                 C   sl   g }|D ]^}t |dkr |d nd}t|tr\t|tr\t|j|j}t||j|j|d< q|| q|S )zy
        Combines together sequences of `StaticMatrixTransform`s into a single
        transform and returns it.
        r   rV   N)r^   r+   r   r   r   rC   rD   r[   )r&   rj   ZnewtransZ	currtransZ	lasttransZcombinedmatr   r   r   r     s    

z#CompositeTransform._combine_staticsc           	      C   s|   |}| j D ]l}i }|j D ]>}t||r>t||}|||< qt||rt||}|||< q|jf i |}|||}q
|S r!   )rj   rD   r   rZ   r*   )	r&   r   r   Z
curr_coordrc   ZfrattrsZinter_frame_attr_nmrH   Zcurr_toframer   r   r   r     s    






zCompositeTransform.__call__c                    sv   dd j D tdd D rPtdd D   fdd} rJtnt}nfdd}t}||jjjdS )	ax  
        Return an encapsulated version of the composite transform so that it appears to
        be a single transform.

        The returned transform internally calls the constituent transforms.  If all of
        the transforms are affine, the merged transform is
        `~astropy.coordinates.transformations.DynamicMatrixTransform` (if there are no
        origin shifts) or `~astropy.coordinates.transformations.AffineTransform`
        (otherwise).  If at least one of the transforms is not affine, the merged
        transform is
        `~astropy.coordinates.transformations.FunctionTransformWithFiniteDifference`.
        c                 S   s"   g | ]}t |ts|n| qS r   )r+   r   r   rP   rc   r   r   r   rT     s   z;CompositeTransform._as_single_transform.<locals>.<listcomp>c                 S   s   g | ]}t |tqS r   )r+   r   r   r   r   r   rT     rU   c                 S   s   g | ]}t |ttfqS r   )r+   r   r   r   r   r   r   rT     s   c                    s     rrd S dS  fdd jD }|fddjD  d}tD ]\}|dkrv fdd jD }nfdd| D }fdd| D }j jfi |jf i |}t	||}qPr|d S |S )	N)NNc                    s   i | ]}|t  |qS r   r   rP   r(   from_coor   r   
<dictcomp>  s   zUCompositeTransform._as_single_transform.<locals>.single_transform.<locals>.<dictcomp>c                    s   i | ]}|t  |qS r   r   r   )to_framer   r   r     s   r   c                    s   i | ]}|t  |qS r   r   r   r   r   r   r     s   c                    s"   i | ]\}}| j jv r||qS r   )rC   r   rp   rc   r   r   r     s   c                    s"   i | ]\}}| j jv r||qS r   )rD   r   rp   r   r   r   r     s   )
is_equivalent_framer   r   r\   ru   r   rC   r   rD   _combine_affine_params)r   r   Zmerged_attrZaffine_paramsrQ   Za_attrZb_attrZnext_affine_params)fixed_originrj   )r   rc   r   r   single_transform  s*    


zACompositeTransform._as_single_transform.<locals>.single_transformc                    s    |  |r|| jS  | |S r!   )r   r   r   )r   r   r%   r   r   r     s    
r   )rj   allr   r   r   rC   rD   rO   )r&   r   Ztransform_typer   )r   r&   rj   r   r     s    +z'CompositeTransform._as_single_transform)r   NT)	r   r   r   r   r'   r   r   r   r   r   r   r   r   r   z  s     
r   c           	      C   s   | \}}|\}}|dur*|dur*|| }n|dur6|n|}|dur|durT| |}|durd|jv rd|jv r|jd |jd  }| |  }|d|i}q|| }q|}n|}||fS )a  
    Combine two sets of affine parameters.

    The parameters for an affine transformation are a 3 x 3 Cartesian
    transformation matrix and a displacement vector, which can include an
    attached velocity.  Either type of parameter can be ``None``.
    Nr   )rE   r   r   r   )	r   Znext_paramsMZvecZnext_MZnext_vecZnew_MZnew_vec_velocityZnew_vecr   r   r   r     s"    


r   z#555555z#783001z#d95f02z#7570b3z#1b9e77)&r   r_   r;   r   warningsr   abcr   r   collectionsr   
contextlibr   r   r   Znumpyr   Zastropyr	   r   Zastropy.utils.exceptionsr
   Zmatrix_utilitiesr   __all__r   r    r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>   sN        6d=  310- #)