Method that returns the grating constant at given positions.

        For a grating with constant grating constant, this will just return the
        number input as ``d`` when the element was initialized. For gratings
        where the grating constant varies with position on the facet, this
        calculates the appropriate number for every position.


Modify blaze angle

        In `diffract_photons` the blaze angle is calculated relative
        to the surface of the grating. In cases where that number has
        to be modified (e.g. when the grating bars are not
        perpendicular to the surface) the blaze angle can be modified
        by overriding this function.


Convention to chose the sign for CAT grating orders

        Blazing happens on the side of the negative orders. Obviously, this
        convention is only meaningful if the photons do not arrive
        perpendicular to the grating.


        We'll first collect all the data in numpy arrays and make
        a new table. Might not be the most efficient solution,
        but should work for now.
        Make work first, improve later.

        To-Do: keep absorbed?


Wrap the MARX C module


The incident angle can easily be calculated from e_x and photons['dir'].

        Returns
        -------
        refl_s, refl_p : np.array or float
            Reflection probability for s and p polarized photons.
            Typically, the number will depend on the incident angle and energy
            of each photon and thus the return value will be a vector.


Make a list of photons parallel to optical axis


project in the y,z plane (the plane of the grating) and calculate angle.


make a photons table - fresh for every test


Format any MARXS object as a json string.

    This method will inspect the object that is passed in and select the
    correct plotting method for its shape.


Recursively output three.js json to describe all elements of a container.

    Output of each element can be a dict (if it is a leaf) or a list
    (if it is a container). We need to flatten the list here to avoid
    arbitrarily deep recursion.


Describe a box-shaped optical elements.


Describe a plane with a hole, such as an aperture of baffle.


Describe a (possibly incomplete) torus.

    The plot range for theta and phi is taken from the values for ``coo1`` and ``coo2``
    in the ``display`` dictionary. There entries should be a list of value if less then
    the full torus (ranging from 0 to 2 pi in each coordinate) is desired for the plot.


Plot lines for simulated rays.

    Parameters
    ----------
    data : np.array of shape(n, N, 3)
        where n is the number of rays, N the number of positions per ray and
        the last dimension is the (x,y,z) of an Eukledian position vector.
    scalar : None or nd.array of shape (n,) or (n, N)
        This quantity is used to color the rays. If ``None`` all rays will have the same
        color. If it has n elements, each ray will have exactly one color (e.g. color
        according to the energy of the ray), if it has n*N elements, rays will be
        multicolored.
    prop : dict
        keyword arguments for line material.
    name : string
        Identifier "name" for three.js objects. This only matters if your website
        identifies elements by name for interactive features.
    cmap : `matplotlib.colors.Colormap` instance or string or None
        `matplotlib` color maps are used to convert ``scalar`` values to rgb colors.
        If ``None`` the default matplotlib colormap will be used, otherwise the colormap
        can be specified in this keyword.


Add metadata and write json for three.js to disk

    Parameters
    ----------
    fileobject : writeable file-like object
    data : list of dict or single dict
        Output of ``xxx.plot(format='threejsjson')`` calls. This can either
        be a list of dictionaries or a single dictionary.
    photons : `astropy.table.Table` or None
        Some metadata is copied from a photon list, if available.


Construct a string that can be pasted into a javascript template to describe a three.js material.

    Parameters
    ----------
    display : dict
        Dictionary of properties. All properties that have a same name as the property of
        the relevant material in javascript are included, all others are ignored.
    material : string
        Name of the material in three.js (Only materials used currently in marxs are supported).

    Returns
    -------
    spec : dict
        material specification


Construct a string that can be pasted into a javascript template to describe a three.js material.

    Parameters
    ----------
    display : dict
        Dictionary of properties. All properties that have a same name as the property of
        the relevant material in javascript are included, all others are ignored.
    material : string
        Name of the material in three.js (Only materials used currently in marxs are supported).

    Returns
    -------
    spec : string
        String that can be pasted into javasript files.


Plot lines for simulated rays.

    Parameters
    ----------
    data : np.array of shape(n, N, 3)
        where n is the number of rays, N the number of positions per ray and
        the last dimension is the (x,y,z) of an Eukledian position vector.
    scalar : None or nd.array of shape (n,) or (n, N)
        This quantity is used to color the rays. If ``None`` all rays will have the same
        color. If it has n elements, each ray will have exactly one color (e.g. color
        according to the energy of the ray), if it has n*N elements, rays will be
        multicolored.
    outfile : file object
        Output javascript code is written to this file.
    prop : dict
        keyword arguments for line material.
    cmap : `matplotlib.colors.Colormap` instance or string or None
        `matplotlib` color maps are used to convert ``scalar`` values to rgb colors.
        If ``None`` the default matplotlib colormap will be used, otherwise the colormap
        can be specified in this keyword.


Plot any MARXS object with three.js as backend.

    This method will inspect the object that is passed in and select the
    correct plotting method for its shape. Javascript code to generate the correct
    represenation in three.js is added to the open file ``outfile``.


Recursivey output three.js commands to print all elements of a container.

    Output of each element can be a dict (if it is a leaf) or a list
    (if it is a container). We need to flatten the list here to avoid
    arbitrarily deep recursion.


Output three.js commands to include a box-shaped optical element.


Output commands for a plane with an inner hole.


Output commands to display (part of) a torus.

    The plot range for theta and phi is taken from the values for ``coo1`` and ``coo2``
    in the ``display`` dictionary. There entries should be a list of value if less then
    the full torus (ranging from 0 to 2 pi in each coordinate) is desired for the plot.


Plot a plane with an inner hole such as an aperture.


Plot a parametric surface.

    The parameter boundaries are taken from the ``coo1`` and ``coo2`` in the
    display dictionary. The plotting routine is generic. It calls the
    ``parametric_surface()`` method of the object that is plotted; see there
    for a detailed description of parameters.


Plot a rectangular box for an object.

    By default, the box extends in x,y, and z direction. The display keyword
    "box-half" can be used to show only one half of the box, e.g. "+x" would
    show  the full extend in y and z direction, but only the lower half in
    x direction (such that rays coming from the +x direction are
    visible up to the interaction point).
    Use this for elements such as mirrors or detectors where
    photon interaction happens on the surface, not in the substrate.


Plot a cylinder for an object.

    The radius is taken from the y dimensions, the length of the tube from
    the x dimension.
    The display keyword "tube-sides" (default: 20) sets the number of vortices
    that are used to approximate the curve.


Plot lines for simulated rays.

    Parameters
    ----------
    data : np.array of shape(n, N, 3)
        where n is the number of rays, N the number of positions per ray and
        the last dimension is the (x,y,z) of an Euclidean position vector.
    scalar : None or nd.array of shape (n,) or (n, N)
        This quantity is used to color the rays. If ``None`` all rays will have
        the same color. If it has n elements, each ray will have exactly one
        color (e.g. color according to the energy of the ray), if it has n*N
        elements, rays will be multicolored.
    viewer : ``mayavi.core.scene.Scene instance``.
       If None, the source is not added to any figure, and will be added
        automatically by the modules or filters.  If False, no figure will be
        created by modules or filters applied to the source: the source can
        only be used for testing, or numerical algorithms, not visualization.
    kwargssurface : dict
        keyword arguments for ``mayavi.mlab.pipeline.surface``

    Returns
    -------
    out : mayavi ojects
        This just passes through the information returned from the mayavi calls.


Plot any marxs object with using Mayavi as a backend.

    This method will inspect the object that is passed in and select the
    correct plotting method for its shape. The object is added to the
    mayavi scene specified in the ``viewer``.

    Parameters
    ----------
    obj : `marxs.base.MarxsElement`
        The element that should be plotted.
    display : dict of None
        Dictionary with display settings. If this is ``None``, ``obj.display``
        is used. If that is also ``None`` then the objects is skipped.
    viewer : ``mayavi.core.scene.Scene``
        If None, the source is not added to any figure, and will be added
        automatically by the modules or filters.  If False, no figure will be
        created by modules or filters applied to the source: the source can
        only be used for testing, or numerical algorithms, not visualization.
    kwargs
        All other parameters will be passed on to the individual plotting
        method.

    Returns
    -------
    out : mayavi object
        Return the result of a mayavi plotting method.


Write to an HTML archive with local copies of js and css requirements.

        This zip file can be shared with others and opened in a web browser.
        In particular, it can be used for journal submission for journals
        that accept interactive js-based content such as ApJ or AJ.

        Parameters
        ----------
        base_name : str
            Name of the file to create, including the path,
            minus any format-specific extension.

        format : {"zip", "tar", "gztar", "bztar", "xztar"}
            The archive format, see `shutil.make_archive` for details.

        *args : tuple
            Other arguments are passed to `shutil.make_archive`.

        **kwargs : dict, optional
            Other keyword arguments are passed to `shutil.make_archive`


Plot a set of triangles.

    Parameters
    ----------
    xyz : np.array
        Euclidean coordinates of nodes
    index : np.array
        index of nodes for each triangle
    display : dict of None
        Dictionary with display settings.
    scene : `marxs.visualization.x3d.Scene` object
        A scene that this object is added to.
        If `None`, a new scene will be created.

    Returns
    -------
    scene : `marxs.visualization.x3d.Scene` object
        Scene with object added.


Plot a plane, e.g. an aperture with an inner hole.


Recursively plot objects contained in a container.


Plot any marxs object with using X3D as a backend.

    This method will inspect the object that is passed in and select the
    correct plotting method for its shape. The object is added to the
    x3d scene specified in the ``scene``.

    Parameters
    ----------
    obj : `marxs.base.MarxsElement`
        The element that should be plotted.
    display : dict of None
        Dictionary with display settings. If this is ``None``, ``obj.display``
        is used. If that is also ``None`` then the objects is skipped.
    scene : `marxs.visualization.x3d.Scene` object
        A scene that this object is added to.
        If `None`, a new scene will be created.
    kwargs
        All other parameters will be passed on to the individual plotting
        method.

    Returns
    -------
    scene : `marxs.visualization.x3d.Scene` object
        Scene with object added.


Plot lines for simulated rays.

    Parameters
    ----------
    data : np.array of shape(n, N, 3)
        where n is the number of rays, N the number of positions per ray and
        the last dimension is the (x,y,z) of an Euclidean position vector.
    scalar : None or nd.array of shape (n,) or (n, N)
    scene : `marxs.visualization.x3d.Scene` object
        A scene that rays are added to.
        If `None`, a new scene will be created.
    normalize_kwargs : dict
        Keyword arguments for the normalization of the scalar quantity
        see `matplotlib.colors.Normalize` for accepted keywords.

    Returns
    -------
    scene : `marxs.visualization.x3d.Scene` object
        Scene with object added.


Look up a plotting routine for an object and execute it.

    This function is not meant to be called directly by the user, instead, it
    is designed to simplify the implementation of new plotting backends.

    Parameters
    ----------
    plot_registry : dict
        Keys are the names of the shape of an object and values in this
        dictionary are functions that know how to plot this type of shape. The
        appropriate plotting function is then called with the input `obj`,
        `display` and any other keyword arguments.
        If the shape is ``"None"`` (as a string), no plotting function is
        called.
    obj : `marxs.base.MarxsElement`
        The element that should be plotted.
    display : dict of None
        Dictionary with display settings. If this is ``None``, ``obj.display``
        is used. If that is also ``None`` then the objects is skipped.
    kwargs : other keyword arguments
        These arguments are just passed through to the plotting function.

    Returns
    -------
    out : backend-dependent
        The output from the plotting function that was executed is passed
        through.  Different plotting backends return different kinds of output.


Convert color tuple to hex string.

    Parameters
    ----------
    color : tuple
        tuple has three elements (rgb) that are floats between 0 and 1
        or ints between 0 and 255.

    Returns
    -------
    hexstring : string
        string encoding that number as hex


Triangulation of a plane with an inner hole

    This function constructs a triangulation for a plane with an inner
    hole, e.g. a rectangular plane where an inner circle is cut out.

    Parameters
    ----------
    outer, inner : np.ndarray of shape (n, 3)
        Coordinates in x,y,z of points that define the inner and outer
        boundary.  ``outer`` and ``inner`` can have a different number of
        points, but points need to be listed in the same orientation
        (e.g. clockwise) for both and the starting points need to have a
        similar angle as seen from the center (e.g. for a plane with z=0, both
        ``outer`` and ``inner`` could list a point close to the y-axis first).

    Returns
    -------
    xyz : nd.array
        stacked ``outer`` and ``inner``.
    triangles : nd.array
        List of the indices. Each row has the index of three points in ``xyz``.

    Examples
    --------
    In this example, we make a square and cut out a smaller square in the
    middle.

    >>> import numpy as np
    >>> from marxs.visualization.utils import plane_with_hole
    >>> outer = np.array([[-1, -1, 1, 1], [-1, 1, 1, -1], [0, 0, 0, 0]]).T
    >>> inner = 0.5 * outer
    >>> xyz, triangles = plane_with_hole(outer, inner)
    >>> triangles
    array([[0, 4, 5],
           [1, 0, 5],
           [1, 5, 6],
           [2, 1, 6],
           [2, 6, 7],
           [3, 2, 7],
           [3, 7, 4],
           [0, 3, 4]])


Combine two disjoint triangulations into one set of points

    This function combines two entirely separate triangulations into
    one set of point and triangles. Plotting the combined
    triangulation should have the same effect as plotting each
    triangulation separately. This function is used for plotting
    apertures where we have e.g. an open ring. This can be plotted as
    an inner circle plus an outer shape with a hole in it.

    Parameters
    ----------
    list_xyz : list of `np.array`
        Each array holds xyz values for one triangulation
    list_triangles : list of nd.array
        Each array holds the list of the indices for one triangulation.

    Returns
    -------
    xyz : nd.array
        stacked ``outer`` and ``inner``.
    triangles : nd.array
        List of the indices. Each row has the index of three points in ``xyz``.


Calculate the boundaries of an order-sorting region

        Parameters
        ----------
        energy : `~astropy.units.quantity.Quantity`
            Energy (or wavelength actually) for which the OSIP should
            be calculated, e.g. the mid-point of a channel
        order : int
            Diffraction order.

        Returns
        -------
        osip_tab : `~astropy.units.quantity.Quantity`
            The shape is an array that contains, for each point in `energy`,
            the width of the OSIP region, as measured from the nominal
            energy. If, for example, the energy is 1 keV and ``osip_tab``
            is `[.1, .2] * u.keV`, then the osip is [.9, 1.2] keV.


Calculate the boundaries of an order-sorting region

        This method returns the boundaries of an order-sorting region in
        energy space.
        This is very similar to `osip_tab`, which gives the width of the
        region. Essentially, this method calls `osip_tab` and add the energy
        so that the result contains the lower and upper bound.

        Parameters
        ----------
        chan_mid : `~astropy.units.quantity.Quantity`
            Energy or wavelength for which the OSIP should
            be calculated, e.g. the mid-point of a channel
        order : int
            Diffraction order.

        Returns
        -------
        osip : `~astropy.units.quantity.Quantity`
            The shape is an array that contains, for each point in `energy`,
            the lower and upper bound of the OSIP region.


Calculate the relative effective area after order sorting.

        This method can be used to calculate a fraction fo

        Parameters
        ----------
        chan_mid_nominal : `~astropy.units.quantity.Quantity`
            Energy or wavelength of the nominal order for which the OSIP should
            be calculated, e.g. the mid-point of a channel
        o_nominal : int
            Nominal diffraction order, i.e. the order that is to be extracted
            from the CCD.
        o_true : int
            True diffraction order.

        Returns
        -------
        osip : `~astropy.units.quantity.Quantity`
            The shape is an array that contains, for each point in `energy`,
            the lower and upper bound of the OSIP region.


Modify an ARF to account for incomplete order sorting

        This function reads an input ARF file, which contains the
        effective area for a grating order in Arcus. For a given order
        sorting window, it then calculates what fraction of the
        photons is lost. For example, if the order-sorting regions can be
        is chosen to contain  90% energy fraction, then the new ARF values
        will be 0.9 (the integrated probability over the order-sorting region)
        times the input ARF.

        If the ``order`` of the new ARF differs from the order of the
        input ARF, then the new ARF is representative of order
        confusion, e.g. is shows how many photons of order 4 are
        sorted into the order=5 grating spectrum.

        Parameters
        ----------
        inputarf : string
            Filename and path of input arf
        outpath : string
            Location where the output arfs are deposited
        order : int
            Nominal order. (The true order of the input arf is taken from the
            input arf header data.)
        outfile : string
            path and filename for output file
        overwrite : bool
            Overwrite existing files?


Plot banana plot with OSIP region marked.

        Parameters
        ----------
        ax : `matplotlib.axes._subplots.AxesSubplot`
            The axes into which the banana is plotted.
        grid : `~astropy.units.quantity.Quantity`
            Wavelength grid in m lambda
        order : int
            Order number
        kwargs :
            Any other parameters are passed to ``plt.plot``


Plot relative contribution of main order and interlopers.

        Parameters
        ----------
        ax : `matplotlib.axes._subplots.AxesSubplot`
            The axes into which the lines are plotted.
        grid : `~astropy.units.quantity.Quantity`
            Wavelength grid in m lambda
        order : int
            Order number


Write summary plot to directory with ARFs

        Parameters
        ----------
        inputarf : string
            Path to one input ARF. The energy grid for the plot
            is taken from that ARF.
        outpath : string
            Location where the output ARFs are deposited
        outroot : string
            prefix for output filename


Generate a plot of the summed effective area.

        This plot is not meant to convey detailed information, but rather
        serve as a check for verification.

        Parameters
        ----------
        path : string
            Path to location of ARFs. The plot is written to the same file.


Apply OSIP to many arfs at once

        This routine iterates over orders and offset orders to produce
        arfs that describe the contamination due to insufficient order
        sorting.  When a single order is extracted (e.g. order 5), but
        the CCD resolution is insufficient to really separate the
        orders well, then some photons from order 4 and 6 might end up
        in the extracted spectrum. This function generates the
        appropriate arfs.

        Input arfs need to follow the arcus filename convention and
        all be located in the same directory. In addition to the ARFs
        with order-sorting applied, this method also places an
        overview plot in the output directory.

        Parameters
        ----------
        inpath : string
            Directory with input ARFs.
        outpath : string
            Location where the output ARFs are deposited
        orders : list of int
            Nominal CCD orders to be processed
        inroot : string
            prefix for input filename
        outroot : string
            prefix for output filename
        overwrite : bool
            Overwrite existing files?
        filename_from_meta_kwargs : dict
            Any additional kwargs needed to run ``self.filename_from_meta``
            and thus pick the correct input arfs in the `inpath`


        Parameters
        ----------
        waveedges : `~astropy.units.quantity.Quantity`
            Edges of channels


        Paramters
        ---------
        TLMIN : int or `None`
            If `None` then the TLMIN# keywords should already be set, if not,
            if is a convenient way to set them on writing.


Return rmf over the full range of channels

        Parameters
        ----------
        energy : `astropy.quantity.Quantity`

        Returns
        -------
        e_mid : array
            mid-points of energy bins in keV
        rmf : array
            rmf value in each bin


Return rmf over channels where values is non-zero

        Parameters
        ----------
        energy : `astropy.quantity.Quantity`

        Returns
        -------
        e_min, e_max : array
            min and max of energy bins in keV
        rmf : array
            rmf value in each bin


Split an array into channel groups for an RMF

        The format of an RMF is set by the [OGIP cal/gen/92-002]_.
        For efficiency, not the entire RMF matrix is stored in the
        rmf files, but only the non-zero components. So, for each
        row (each row contains the matrix for one energy channel)
        the elements that are zero or below a threshold are removed
        from the matrix. This leaves one or more "channel subsets"
        and extra index arrays are needed to specify the position
        and lengths of these channel subsets in the full array.

        [OGIP cal/gen/92-002]_ discusses how arrays can be stored
        as fixed-length or as variable-length arrays in a fits file.
        Since this function operates on a single row only, it return
        lists. Formatting in a fixed-length array, if needed, has
        to be done separately.

        References
        ----------
        .. [OGIP cal/gen/92-002] https://heasarc.gsfc.nasa.gov/docs/heasarc/caldb/docs/memos/cal_gen_92_002/cal_gen_92_002.html

        Parameters
        ----------
        arr : `np.array`
            RMF respsonse matrix over all channels of the isntrument
        threshold : float
            Array values below the threshold will be removed.

        Returns
        -------
        N_GRP : int
            Number of channel subsets
        F_CHAN : list
            First channel in each subset (1 indexed)
        N_CHAN : list
            Number of channels in each subset
        MATRIX : list
            Matrix elements


Convert variable length arrays to fixed length arrays

        Astropy cannot write variable length arrays
        (https://github.com/astropy/astropy/issues/11323 and linked isues).
        Until that is fixed, all arrays need to be converted to fixed length
        for writing.

        Note: In many cases, this will only moderately increase the file size
        but speed up operations significantly, so it might be a good idea
        anyway.


Make RMF for a Gaussian redistribution

        This is a convenience function for a common, simple case.
        For more complex parameterization of the redistributions, see
        `marxs.reduction.lsfparm`.

        Parameters
        ----------
        bin_edges : `~astropy.units.quantity.Quantity`
            Edges of the channels in energy or wavelength. The RMF that is
            generated from n edges will have n-1 bins.
        sigmatab : `~astropy.units.table.Table`
            sigma for a Gaussian in a table with columns ``'wave'``
            and ``'sigma_wave'`` or ``'energy'`` and ``sigma_energy'``.
            In the latter case, the information is converted to wavelength space.
            Note that a Gaussian in energy space is not the same as in wavelength space,
            so this should only be used if this approximate treatment is sufficient.
        threshold : float
            To reduce the file size, RMF components below the threshold value
            are cut-off
        headerkw : dict
            Keywords arguments for the header of the RMF file. Minimum required list
            are the header keywords SATELLIT, TELESCOP, INSTRUME, FILTER


Convert CALDB entries to Sherpa model

    Convert entries in a CALDB lsfparm files to a Sherpa model.  The
    lsfparm files can contain several rows, for different off-axis
    angles and radii and in each row there will be entries for a
    number of wavelength points and extraction width.

    This function expects as input the index numbers for row,
    wavelength as index for the wavelength array etc. In practical
    applications, the CALDB file will be queried for a specific
    position, wavelength etc., but for development purposes it is
    useful to go into the raw array, e.g. to read some unrelated CALDB
    file (say for a different detector) to use as a starting point to
    fit the lsfparm parameters or to plot different settings for
    comparison.

    caldb : `astropy.table.Table`
        CALDB lsfparm table


shmodel : Sherpa model instance


    Parameters
    ----------
    width : np.array
        extraction width
    waves
    model : Sherpa model instance
        This instance is inspected for the name of the model components to
        set up the correct columns


Get interpolating functions from values in LSFPARM

    Parameters
    ----------
    row : `astropy.table.Row`
        Table row from CALDB LSFPARM file
    **kwargs
        All other keywords arguments are passed to
        `scipy.interpolate.interp1d` or `scipy.interpolate.interp2d`.


    makes PHA channels (because that's what the lsfparm files are made for)
    I made a choice here: writing with TLMIN=0, which is valid OGIP


Fit an LSF model to a simulated set of photons

    Photons are binned into a histogram and the model is fit to that.
    Since the histogram is constructed using the "probability" column
    in the event list, if will contain non-integer data, which does not
    follow a Poisson distribution.
    Unfortunately, the statistical error in each bin is not well defined,
    but some number is needed for the fit. So, the square root of the
    number of photons in each bin (with a floor of 1) is used.
    This is not ideal, but it gives satisfactory results for typical cases.

    evt : `astropy.table.Table`
        Table with photons. Must have columns 'probability' and 'colname'.
        This table must already be filtered to contain only photons
        that are part of the LSF (e.g. filter on order and wavelength)
    model : `sherpa.models.model.Model`
        LSF model to be fit.
    wavebin : float
        Wavelength bin size for histogram
    d_wave : float
        Half-width of wavelength range to fit
    colname : str
        Name of column in evt that contains the wavelength. The default
        name is taken from the Chandra terminaology.
    stat : `sherpa.stats.Stat`
        If None, use `sherpa.stats.LeastSq`
    method : `sherpa.optmethods.OptMethod`
        If None, use `sherpa.optmethods.LevMar`

    Returns
    -------
    sdata : `sherpa.data.Data1D`
        Data object with the histogram
    res : `sherpa.fit.FitResults`
        Results of the fit


Plot an LSF fit for visual inspection

    Parameters
    ----------
    sdata : `sherpa.data.Data1D`
        Data object with the histogram
    model : `sherpa.models.model.Model`
        LSF model from the fit to be plotted
    axes : list of `matplotlib.axes.Axes`
        The first axis is used linar plot, highligting the fit to the
        line center and the second axis for a log plot, highlighting the
        wings.


Make an RMF for testing. Using one possible method
    to generate all the required inputs.


Find the radius the photons coming from a single mirror shell have.

    For nested Wolter Type I mirrors the ray of photons reflected from a single
    mirror shell essentially form a cone in space. The tip of the cone is at
    the focal point and the base is at the mirror. There is a certain thickness
    to this cone depending on where exactly on the mirror the individual photon
    was reflected.

    This function takes a photon list of photons after passing through the
    mirror and calculates the radius range that this photon cone covers at a
    specific distance from the focal point. This information can help to design
    the placement of gratings.

    Parameters
    ----------
    photons : `~astropy.table.Table`
        Photon list with position and direction of photons leaving the mirror
    mirror_shell : int
        Select mirror shell to look at (uses column ``mirror_shell`` in
        ``photons`` for filtering).
    x : float
        Distance along the optical axis (assumed to coincide with the x axis
        with focal point at 0).
    percentile : list of floats
        The radius is calculated at the given percentiles. ``50`` would give
        the median radius. The default of ``[1, 99]`` gives a radius range
        excluding extreme outliers such as stray rays scattered into the extreme
        wing of the PSF.


Quartic torus equation.

        Roots of this equation are points on the torus.

        Parameters
        ----------
        xyz : np.array of shape (N, 3) or (3)
            Coordinates of points in euclidean space. The quartic is calculated
            for those points.
        transform : bool
            If ``True`` transform ``xyz`` from the global coordinate system
            into the local coordinate system of the torus. If this
            transformation is done in the calling function already, set to
        ``False``.

        Returns
        -------
        q : np.array of shape (N) or scalar
            Quartic at the input location


Solve the quartic on the Rowland torus.

        This method solves the quartic equation for positions on the Rowland
        Torus, i.e. it intersects a line with the torus. To that end, the
        location on the line is varied and the root of the quartic is found
        through numerical optimization.

        Parameters
        ----------
        origin : np.array
            Origin of line as homogeneous coordinate.
            This is also used as approximate starting point for the numerical
            optimization, so it should be reasonably close to the
            solution.
        v : np.array
            Direction of the line as homogeneous coordinate.
        transform : bool
            If ``True`` transform input from the global coordinate system
            into the local coordinate system of the torus. If this
            transformation is done in the calling function already, set to
            ``False``.

        Returns
        -------
        coo : np.array
            Position of intersection as homogeneous coordinate.


Parametric representation of surface of torus.

        In contrast to `parametric` the input parameters here are 1-d arrays
        and the functions converts that to a rectangular grid.

        Parameters
        ----------
        theta : np.array
            Points on the Rowland circle (specified in rad, where 0 is on the
            optical axis across from the focal point).
        phi : np.array
            ``phi`` rotates the Rowland circle to make up the Rowland torus.

        Returns
        -------
        xyzw : np.array
            Torus coordinates in global homogeneous coordinate system.


Parametric description of points on the torus.

        This is just another way to obtain the shape of the torus, e.g.
        for visualization.

        Parameters
        ----------
        theta : np.array
            Points on the Rowland circle (specified in rad, where 0 is on the
            optical axis across from the focal point).
        phi : np.array
            ``phi`` rotates the Rowland circle to make up the Rowland torus.

        Returns
        -------
        xyzw : np.array
            Torus coordinates in global homogeneous coordinate system.


Calculate (theta, phi) coordinates for point on torus surface.

        Parameters
        ----------
        xyzw : np.array of shape (N, 4)
            Coordinates of points on the torus surface in homogeneous
            coordinates.

        transform : bool
            If ``True`` transform ``xyz`` from the global coordinate system
            into the local coordinate system of the torus. If this
            transformation is done in the calling function already, set to
            ``False``.

        intersectvalid : bool
            When ``r >=R`` the torus can intersect with itself. At these points,
            phi is not unique. If ``intersectvalid`` is true, those points will
            be filled with an arbitrarily chosen valid value (0.), otherwise
            they will be nan.

        Returns
        -------
        theta, phi: np.array
            Parametric representation for all points on the torus.


Return the gradient vector field.

        Following the usual convention, the vector is pointing outwards
        of the torus volume.

        Parameters
        ----------
        theta, phi : 1-d np.arrays
            Theta and phi coordinates for points on the torus surface.

        Returns
        -------
        gradient : np.array
            Gradient vector field in homogeneous coordinates. One vector
            corresponds to each input point. The shape of ``gradient`` is the
            same as the shape of ``xyz``.


Return the gradient vector field.

        Following the usual conventions, the vector is pointing outwards
        of the torus volume.

        Parameters
        ----------
        xyzw : np.array of shape (N, 4)
            Coordinates of points in euclidean space. The quartic is calculated
            for those points. All points need to be on the surface of the torus.

        Returns
        -------
        gradient : np.array
            Gradient vector field in euclidean coordinates. One vector
            corresponds to each input point. The shape of ``gradient`` is the
            same as the shape of ``xyz``.


Get Cartesian coordinates for radius, angle on the rowland circle.

        y, z are calculated from the radius and angle of polar coordinates in a
        plane; then x is determined from the condition that the point lies on
        the Rowland circle.  The plane is perpendicular to the optical axis
        that defines the Rowland circle.

        Parameters
        ----------
        radius, angle : float or np.array of shape (n,)
            Polar coordinates in a plane perpendicular to the optical axis
            (where the optical axis is parallel to the x-axis and goes through
            the origin of the `RowlandTorus`.
            ``angle=0`` coincides with the local y-axis.
        start : number
            Starting value for the search. The quartic can have up to four
            solutions because a line can intersect a torus in four points.
            The solution found thus depends on where the numerical root solver
            starts the search.

        Returns
        -------
        xyz : np.array of shape (n, 3)
            Euclidean coordinates in the global coordinate system.


Design a torus with specifications similar to Heilmann et al. 2010

    A `RowlandTorus` is fully specified with the parameters ``r``, ``R`` and
    ``pos4d``.  However, in practice, these numbers might be derived from other
    values.  This function calculates the parameters of a RowlandTorus, based
    on a different set of input values.

    Parameters
    ----------
    f : float
        distance between focal point and on-axis grating. Should be as large as
        possible given the limitations of the spacecraft to increase the
        resolution.
    alpha : float (in radian)
        angle between optical axis and the line (on-axis grating - center of
        Rowland circle).
        A typical value could be twice the blaze angle.
    beta : float (in radian)
        angle between optical axis and the line (on-axis grating - hinge),
        where the hinge is a point on the Rowland circle. The Rowland Torus
        will be constructed by rotating
        the Rowland Circle around the axis (focal point - hinge).
        The center of the Rowland Torus will be the point where the line
        (on-axis grating - center of Rowland circle) intersects the line
        (focal point - hinge).

    Returns
    -------
    R : float
        Radius of Rowland torus. ``r`` determines the radius of the Rowland
        circle, ``R`` is then used to rotate that circle around the axis of
        symmetry of the torus.
    r : float
        Radius of Rowland circle
    pos4d : np.array of shape (4, 4)

    Notes
    -----
    The geometry used here really needs to be explained in a figure.
    However, some notes to explain at least the meaning of the symbols on the
    code are in order:

    - Cat : position of on-axis CAT grating (where the Rowland circle intersects the on-axis beam)


This prescription is borne out of the engineering needs where it turns out
    that the spacing between channels d_BF is actually an important parameter
    so it makes sense to use that here to parameterize the torus, too.

    f is the distance between a theoretical on-axis CAT grating and the focal
    point.  It's default is a little smaller than 12 m, to leave space to mount
    the SPOs.

    Parameters
    ----------
    d_BF : float
        Distance between the optical axes of two channels measured
        in the dispersion direction.
    R : float
        Radius of Rowland torus.
    f : float
        Distance between a theoretical on-axis CAT grating and the focal
        point.

    Returns
    -------
    geometry : dict
        Dictionary with the keys that hold the input parameters,
        derived dimensions of the geometry, and the constructed
        Rowland tori. Three Rowland tori are constructed: One for
        each channel called (rowland_central, rowland_central_m) and
        one for the detector (rowland_detector).


Generate Rowland tori for multiple spectral channels in a double-tori design.

    While this function can generate four separate channels (splitting the
    original and the original mirrored channel in two each).
    Rowland tori are added to the input geometry dictionary.

    Parameters
    ----------
    geometry : dict
        Dictionary with the keys from ` double_rowland_from_channel_distance`.
    offsets : dict
        Dictionary with the keys that hold the channel ids and the
        values that hold the offsets in the x, y, and z direction.
        The default value is set for a total of four channels, where each
        of the original and the original mirrored channel is split in two.
        However, the function can be used to generate any number of channels
        with the naming convention that channels 1, 2, 3, ... are generated
        from the original channel and channels 1m, 2m, 3m, ... are generated
        from the original mirrored channel.


Specify the element position in the yz plane

        For the torus, the Rowland circle lies in the xy plane and the symmetry
        axis of the torus is the y-axis. In this class, the position of the
        elements is specified in the yz plane in the coordinates of the
        torus and the remaining coordinate.

        This function will be customized by derived classes.

        Returns
        -------
        ypos, zpos : np.array
            1D arrays of y and z positions for the elements distributed in 2D.


Generate 3D positions of elements

        For each element, y and z coordinates in the torus coordinate system
        are generated from ``self.elemposyz`` and the elements are placed at
        x=0. Then, that initial coordinate is transformed into the global
        coordinate system and moved by
        ``self.guess_distance * self.optimize_axis``. The resulting position
        is the starting point for numerical optimization along
        ``self.optimize_axis`` to find the on-torus location.


Distribute elements on a radius.

        Parameters
        ----------
        radius : list of two floats
            inner and outer radius that should be covered by elements

        Returns
        -------
        n : int
            Number of elements needed to cover a given radius segment.
            Elements might reach beyond the radius limits if the difference between
            inner and outer radius is not an integer multiple of the element size.


Distributes elements as evenly as possible along a radius.

        .. note::
           Unlike `distribute_elements_on_arc`, this function will have
           elements reaching beyond the limits of the radius, if the distance
           between inner and outer radius is not an integer multiple of the
           element size.

        Returns
        -------
        radii : np.ndarray
            Radii of the element *center* positions.


Calculate maximal number of elements that can be placed at a certain radius.

        Parameters
        ----------
        radius : float
            Radius of circle where the centers of all elements will be placed.


Distribute elements on an arc.

        The elements are distributed as evenly as possible over the arc.

        .. note::

          Contrary to `distribute_elements_on_radius`, elements never stretch
          beyond the limits set by the ``phi`` parameter of the GAS. If an arc
          segment is not wide enough to accommodate at least a single element,
          it will go empty.
          (The exception to that is is ``phi`` is a single value. In that case,
          there will be exactly one element.)

        Parameters
        ----------
        radius : float
            radius of arc where the elements are to be distributed.

        Returns
        -------
        centerangles : array
            The phi angles for centers of the elements at ``radius``.


Bend gratings to follow the Rowland cirle.

    Gratings are bend in one direction (the dispersion direction) only.

    The numerical procedure used to calculate the bending takes the central ray as
    a fixed ppoint assuming that the central ray always goes to the correct position!

    Parameters
    ----------
    gratings : list
        List of gratings to be bend
    radius : float
        Radius of the newly bend gratings


    Parameters
    ----------
    gratings : list

    optimizer : callable


Decorator for functions that modify optical elements.

    The functions in this module are written to work on a single optical
    element. This decorator allows them to accept a list of elements or
    a single element.


Move and rotate elements around principal axes.

    Parameters
    ----------
    e : `marxs.simulator.Parallel` or list of those elements
        Elements where uncertainties will be set
    dx, dy, dz : float
        accuracy of positioning in x, y, z (in mm) - Gaussian sigma, not FWHM!
    rx, ry, rz : float
        accuracy of positioning. Rotation around x, y, z (in rad) - Gaussian
        sigma, not FWHM!


Move and rotate origin of the whole `marxs.simulator.Parallel` object.

    Parameters
    ----------
    e :`marxs.simulator.Parallel` or list of those elements
        Elements where uncertainties will be set
    dx, dy, dz : float
        translation in x, y, z (in mm)
    rx, ry, rz : float
        Rotation around x, y, z (in rad)


Move and rotate all elements of `marxs.simulator.Parallel` object.

    Unlike `~marxs.design.tolerancing.moveglobal` this does not move to
    center of the `~marxs.simulator.Parallel` object, instead it moves
    all elements individually. Unlike `~marxs.design.tolerancing.wiggle`,
    each element is moved by the same amount, not a number drawn from from
    distribution.

    Parameters
    ----------
    e :`marxs.simulator.Parallel` or list of those elements
        Elements where uncertainties will be set
    dx, dy, dz : float
        translation in x, y, z (in mm)
    rx, ry, rz : float
        Rotation around x, y, z (in rad)


Move and rotate a marxs element around principal axes.

    Unlike `~marxs.design.tolerancing.moveglobal` this does not work on a
    `~marxs.simulator.Parallel` object, which is a container holding other
    elements, but it moves just one specific element itself (e.g. a single
    detector).

    To make it easier to return the element to its original position,
    the original ``pos4d`` matrix of the element is stored as ``pos4d_orig``.

    Parameters
    ----------
    e :`marxs.optics.base.OpticalElement` or list of those elements
        Elements where uncertainties will be set
    dx, dy, dz : float
        translation in x, y, z (in mm)
    rx, ry, rz : float
        Rotation around x, y, z (in rad)


Modify the attributes of an element.

    This function modifies the attributes of an object. The keyword arguments
    are name and value of the attributes to be changed. This can be used for
    a wide variety of MARXS objects where the value of a parameter is stored
    as an instance attribute.

    Parameters
    ----------
    element : object
        Some optical component.
    keywords : it depends

    Examples
    --------
    In a `marxs.optics.RadialMirrorScatter` object, the width of the scattering
    can be modified like this:

    >>> from astropy import units as u
    >>> from marxs.optics import RadialMirrorScatter
    >>> from marxs.design.tolerancing import varyattribute
    >>> rms = RadialMirrorScatter(inplanescatter=2 * u.arcsec, perpplanescatter=0 * u.arcsec)
    >>> varyattribute(rms, inplanescatter=1 * u.arcsec)

    It is usually easy to see which attributes of a
    `marxs.simulator.SimulationSequenceElement` are relevant to be changed in
    tolerancing simulations when looking at the attributes or the implementation
    of those elements.


Randomly draw different grating periods for different gratings

    This function needs to be called with gratings as parameters, e.g.

    >>> from marxs.optics import CATGrating
    >>> from marxs.design.tolerancing import varyperiod
    >>> grating = CATGrating(order_selector=None, d=0.001)
    >>> varyperiod(grating, 2e-4, 1e-5)

    and the parameters are expected to have two components:
    center and sigma of a Gaussian distribution for grating contstant d.

    Parameters
    ----------
    element :`marxs.optics.FlatGrating` or similar (or list of those elements)
        Elements where uncertainties will be set
    period_mean : float
        Center of Gaussian (in mm)
    period_sigma : float
        Sigma of Gaussian (in mm)


Modify the OrderSelector for a grating

    Parameters
    ----------
    element :`marxs.optics.FlatGrating` or similar (or list of those elements)
        Elements where the OrderSelector will be changed
    order_selector : class
        This should be a subclass of `InterpolateRalfTable` which determines how
        the order will be selected. In the case of the default class, the blaze
        angle of an incoming photons will be modified randomly to represent
        small-scale deviations from the flatness of the gratings.
    args, kwargs :
        All other parameters are used to initialize the OrderSelector


Run tolerancing calculations for a range of parameters

    This function takes an instrument configuration and a function to change
    one aspect of it. For every change it runs a simulations and calculates a
    figure of merit. As the name indicates, this function is designed to derive
    alignment tolerances for certain instrument parts but it might be general
    enough for other parameter studies in instrument design.

    Parameters
    ----------
    photons_in : `astropy.table.Table`
        Input photons list. To speed up the computation, it is useful if photon
        list has been run through all elements of the instrument that are
        located before the first element that is toleranced here.
    instrum : `marxs.simulator.Sequence` object
        An instance of the instrument which contains all elements that
        `photons_in` still have to pass. This can include elements that are
        toleranced in this run and those that are located behind them.
    wigglefunc : callable
        Function that modifies the `instrum` with the following calling signature:
        ``wigglefunc(wiggleparts, pars)`` where ``pars`` is one dict from
        `parameters`. Note that this function is called with `wiggleparts` which
        can be the same as `instrum` or just a subset.
    wiggleparts :  `marxs.base.SimulationSequenceElement` instance
        Element which is modified by `wigglefunc`. Typically this is a subset of
        `instrum`. For example, to tolerance the mirror alignments
        `wiggleparts` would just be the mirror objects, while `instrum` would
        contain all the parts of the instrument that the photons need to run
        though up to the detector.
    parameters : list of dicts
        List of parameter values for calls to `wigglefunc`.
    analyzefunc : callable function or object
        This is called with a photon table and should return a dictionary
        of results. This could be, e.g. a
        `marxs.analysis.gratings.CaptureResAeff` instance.

    Returns
    -------
    result : list of dicts
        Each dict contains parameters and results for one run.

    Notes
    -----
    The format of input and output as lists of dicts is chosen because this
    would work well for a parallel version of this function which could have
    the same interface when it is implemented.


Run tolerancing for a grid of parameters and energies

    This function loops over `~marxs.design.tolerancing.run_tolerances` for
    different energies.
    This function takes an instrument configuration and a function to change
    one aspect of it. For every change it runs simulations and calculates a
    figure of merit. As the name indicates, this function is designed to derive
    alignment tolerances for certain instrument parts but it might be general
    enough for other parameter studies in instrument design.

    There are two nested loops here looping over energy and tolerancing
    parameters. In this case, the outer loop is over energies and then for
    every energy, `~marxs.design.tolerancing.run_tolerances` loops over the
    parameters. This works well where running the ``wigglefunc`` is relatively
    fast, but propagating the photons through ``instrum_before`` is slow and
    minimizes the memory footprint, because only one photon list is in memory
    at any one time. It also implies that runs for the same wiggle parameters
    but different energies are run on different realizations of any random
    draws that are performed by ``wigglefunc``.

    Parameters
    ----------
    source : `marxs.source.Source`
        Source used to generate the photons for every energy. This function
        changes the energy of the source, so the source should be set for
        monoenergetic emission with a constant flux of 1.
    energies : `astropy.units.quantity.Quantity`
        An array of energy values.
    instrum_before : `marxs.simulator.Sequence`
        An instance of the instrument which contains all elements **before**
        the first elements in ``wiggleparts``. The first element should be a
        `marxs.source.pointing.PointingModel`.  In principle,
        ``instrum_before`` can be an empty sequence and the entire instrument
        can be defined in ``instrum_remaining``, however, ``instrum_before`` is
        run only once per energy and thus it can greatly speed up the
        simulation to set this.
    instrum_remaining : `marxs.simulator.Sequence`
        An instance of the instrument which contains all elements not included
        in ``instrum_before``. ``instrum_remaining`` should include elements
        that are toleranced in this run and those that are located behind them.
    wigglefunc, wiggleparts, parameters, analyzefunc :
        These parameters are passed to
        `marxs.design.tolerancing.run_tolerances`. See that function for a
        description of these parameters.
    reset : dict or ``None``
        A dictionary of values for the ``wigglefunc`` function that resets
        the positions or properties of the wiggled elements to their default.
        If ``reset=None``, then the elements affected by ``wigglefunc`` will
        be in the state corresponding to the last entry in ``parameters`` when
        this function exits.
    t_source : `astropy.units.quantity.Quantity`
        parameter for ``source.generate_photons()``. If the source flux is set
        to one, then ``t_source`` determines the number of photons used in the
        tolerancing simulation.

    Returns
    -------
    result : `astropy.table.Table`
        Each row in the table contains energy, wave, parameter values, and
        results from ``analyzefunc`` for a single run.


Run tolerancing for a grid of parameters and energies

    This function loops over `~marxs.design.tolerancing.run_tolerances` for
    different energies.
    This function takes an instrument configuration and a function to change
    one aspect of it. For every change it runs a simulations and calculates a
    figure of merit. As the name indicates, this function is designed to derive
    alignment tolerances for certain instrument parts but it might be general
    enough for other parameter studies in instrument design.

    There are two nested loops here looping over energy and tolerancing
    parameters. In this case, the outer loop is over energies and then for
    every energy, `~marxs.design.tolerancing.run_tolerances` loops over the
    parameters. This works well where running the ``wigglefunc`` is relatively
    fast, but propagating the photons through ``instrum_before`` is slow and
    minimizes the memory footprint, because only one photon list is in memory
    at any one time. It also implies that runs for the same wiggle parameters
    but different energies are run on different realizations of any random
    draws that are performed by ``wigglefunc``.

    Parameters
    ----------
    source : `marxs.source.Source`
        Source used to generate the photons for every energy. This function
        changes the energy of the source, so the source should be set for
        monoenergetic emission with a constant flux of 1.
    energies : `astropy.units.quantity.Quantity`
        An array of energy values.
    instrum : `marxs.simulator.Sequence`
        An instance of the instrument which contains all elements.
    cls : class
        Class of modified / wiggled / etc. element
    wigglefunc, parameters, analyzefunc :
        These parameters are passed to
        `marxs.design.tolerancing.run_tolerances`. See that function for a
        description of these parameters.
    reset : dict or ``None``
        A dictionary of values for the ``wigglefunc`` function that resets
        the positions or properties of the wiggled elements to their default.
        If ``reset=None``, then the elements affected by ``wigglefunc`` will
        be in the state corresponding to the last entry in ``parameters`` when
        this function exits.
    t_source : `astropy.units.quantity.Quantity`
        parameter for ``source.generate_photons()``. If the source flux is set
        to one, then ``t_source`` determines the number of photons used in the
        tolerancing simulation.

    Returns
    -------
    result : `astropy.table.Table`
        Each row in the table contains energy, wave, parameter values, and
        results from ``analyzefunc`` for a single run.


Generate a list of parameters for the wiggle functions in this module.

    This modules contains several wiggle functions such as
    `~marxs.design.tolerances.moveglobal` or
    `~marxs.design.tolerances.wiggle` that expect input for 6 degrees of
    freedom, three translations and three rotations. Commonly, we want to
    study and dof at a time. This function helps with generating lists of
    dicts for that purpose.

    Parameters
    ----------
    trans : `astropy.units.quantity.Quantity`
        Steps for translation. The first element should be 0.
    rot : `astropy.units.quantity.Quantity`
        Steps for rotation. The first element should be 0.

    Returns
    -------
    changeglobal : list of dicts
        This list contains changes in positive and negative directions.
        Use this as input for e.g. `~marxs.design.tolerances.moveglobal`.
    changeindividual : list of dicts
        This list contains only one side, so use this as input for
        e.g. `~marxs.design.tolerances.wiggle` where the actual misalignment
        is drawn from a distribution.

    Examples
    --------
    In this example, we take small steps close to 0 and then increase the
    step size for larger distances, going up to a misalignment of 10 mm in
    linear translation and 2 degrees in rotation::

        >>> import astropy.units as u
        >>> from marxs.design.tolerancing import generate_6d_wigglelist
        >>> trans = [0., .1, .2, .4, .7] * u.cm
        >>> rot = [0., 2., 5., 10., 20] * u.arcmin
        >>> lglob, lind = generate_6d_wigglelist(trans, rot)


Select subset of tolerancing table with changes in 1 dof only

    This function selects a subset of a table where all parameters other
    than the parameter selected right now are zero.

    This is a helper function for analyzing and plotting results.

    Parameters
    ----------
    table : `astropy.table.Table`
        Table with wiggle results
    par : string
        Name of parameter to be selected
    parlist : list of strings
        Name of all parameters in ``table``

    Returns
    -------
    tab : `astropy.table.Table`
        Filtered table


Plotting function for overview plot wiggeling 1 dof at the time.

        For parameters starting with "d" (e.g. "dx", "dy", "dz"), the plot axes
        will be labeled as a shift, for parameters tarting with "r" as rotation.

        Parameters
        ----------
        table : `astropy.table.Table`
            Table with wiggle results
        par : string
            Name of parameter to be plotted
        parlist : list of strings
            Name of all parameters in ``table``
        ax : `matplotlib.axes.Axes`
            Axis object to plot into.
        axt : ``None`` or  `matplotlib.axes.Axes`
            If this is ``None``, twin axis are created to show resolving power
            and effective area in one plot. Alternatively, a second axes instance
            can be given here.
        R_col : string
            Column name in ``tab`` that hold the resolving power to be plotted.
            Default is set to work with `marxs.design.tolerancing.CaptureResAeff`.
        Aeff_col : string
            Column name in ``tab`` that hold the effective area to be plotted.
            Default is set to work with `marxs.design.tolerancing.CaptureResAeff`.
        axes_facecolor : any matplotlib color specification
            Color for the background in the plot.


Load a table with wiggle results and make default plot

        This is a function to generate a quicklook image with many
        hardcoded defaults for figure size, colors etc.
        In particular, this function is written for the display of
        6d plots which vary 6 degrees of freedom, one at a time.

        The color for the background in the plot is set depending on the filename
        using the ``string : color`` assignments in
        `~marxs.design.tolerancing.wiggle_plot_facecolors`. No fancy regexp based
        match is applied, this is simply a check with ``in``.

        Parameters
        ----------
        filename : string
            Path to a file with data that can be plotted by
            `~marxs.design.tolerancing.plot_wiggle`.

        parlist : list of strings
            Name of all parameters in ``table``.
            This function only plots six of them.

        Returns
        -------
        tab : `astropy.table.Table`
            Table of data read from ``filename``
        fig : `matplotlib.figure.Figure`
            Figure with plot.
        kwargs :
            All other parameters are passed to
            `~marxs.design.tolerancing.plot_wiggle`.


Generate 4d matrices that represent facet misalignment.

    Positional and rotational uncertainties are input to this function. It then
    draws randomnly from Gaussians centered on 0 (the correct position) for the displacement
    and rotation, where the :math:`\sigma` of the Gaussian is given by the numbers in the input.
    The linear displacements and angles are expressed as (4,4) matrixes suitable for use with
    homogeneous coordinates.

    Parameters
    ----------
    n : int
        Number of 4d matrixes to be calculated
    xyz : tuple of 3 floats
        accuracy of grating positioning in x, y, z (in mm) - Gaussian sigma, not FWHM!
    angle_xyz : tuple of 3 floats
        accuracy of grating positioning. Rotation around x, y, z (in rad) - Gaussian sigma, not FWHM!
    trans_offset : array_like
        will be passed to `numpy.random.normal` as the ``loc`` parameter when drawing translantions.
    rot_offset : array_like
        will be passed to `numpy.random.normal` as the ``loc`` parameter when drawing rotations.

    Returns
    -------
    pos_uncert : list of n (4,4) np.arrays
        Random realizations of the uncertainty


make a parallel structure - fresh for every test


Just another way to specify a torus with y-axis as symmetry


Cutting the torus in a plane that includes the symmetry axis should give a cirlce.

    Check that that's true even for a tilted torus.


Generate Eukledian positions for the corners of a square.

    The square is centered on the center of the object and the edges are
    given by ``v_y`` and ``v_z``.

    Parameters
    ----------
    r_factor : float
        Scaling factor for the square.

    Returns
    -------
    box : np.array of shape (4, 3)
        Eukledian coordinates of the corners of the square in 3d space.


Generate Eukledian positions along an ellipse.

    The circle is centered on the center of the object and the
    semi-major and minor axes are given by ``v_y`` and ``v_z``. Note
    that this function is usually used to generate circle position,
    although ellipses are possible, thus the name.

    The circle (or ellipse) is approximated by a polygon with
    ``n_vertices`` vertices, where the value of ``n_vertices`` is
    taken from the ``self.display`` dictionary.

    Parameters
    ----------
    r_factor : float
        Scaling factor for the square.
    phi_lim : list
        Lower and upper limit for the angle phi to restrict the circle to a
        wedge.

    Returns
    -------
    circle : np.array of shape (n, 3)
        Eukledian coordinates of the corners of the square in 3d space.


Calculate the intersection point between a ray and the element

        Parameters
        ----------
        dir : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of the direction of the ray
        pos : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of a point on the ray

        Returns
        -------
        intersect :  boolean array of length N
            ``True`` if an intersection point is found.
        interpos : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of the intersection point. Values are set
            to ``np.nan`` if no intersection point is found.
        interpos_local : `numpy.ndarray` of shape (N, 2)
            y and z coordinates in the coordiante system of the active plane.


Calculate the intersection point between a ray and the element

        Parameters
        ----------
        dir : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of the direction of the ray
        pos : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of a point on the ray

        Returns
        -------
        intersect :  boolean array of length N
            ``True`` if an intersection point is found.
        interpos : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of the intersection point. Values are set
            to ``np.nan`` if no intersection point is found.
        interpos_local : `numpy.ndarray` of shape (N, 2)
            y and z coordinates in the coordiante system of the active plane
            (not normalized to the dimensions of the element in question, but
            in absolute units).


Return a triangulation of the aperture hole embedded in a square.

        The size of the outer square is determined by the ``'outer_factor'``
        element in ``self.display``.

        Returns
        -------
        xyz : np.array
            Numpy array of vertex positions in Eukeldian space
        triangles : np.array
            Array of index numbers that define triangles


Generate a `Cylinder` from a `RowlandTorus`.

        According to the definition of the `marxs.design.rowland.RowlandTorus`
        the origin phi=0 is at the "top". When this class method is used to
        make a detector that catches all dispersed grating signal on the
        Rowland torus, a ``rotation=np.pi`` places the center of the Cylinder
        close to the center of the torus (the location of the focal point in
        the standard Rowland geometry).

        Parameters
        ----------
        rowland : `~marxs.design.RowlandTorus`
            The circular detector is constructed to fit exactly into the
            Rowland Circle defined by ``rowland``.
        width : float
            Half-width of the tube in the flat direction (z-axis) in mm
        rotation : float
            Rotation angle of the Cylinder around its z-axis compared to the
            phi=0 position of the Rowland torus.


Calculate the intersection point between a ray and the element

        Parameters
        ----------
        dir : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of the direction of the ray
        pos : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of a point on the ray
        transform : bool
            If ``True``, input is in global coordinates and needs to be
            transformed here for the calculations; if ``False`` input is in
            local coordinates.

        Returns
        -------
        intersect :  boolean array of length N
            ``True`` if an intersection point is found.
        interpos : `numpy.ndarray` of shape (N, 4)
            homogeneous coordinates of the intersection point. Values are set
            to ``np.nan`` is no intersecton point is found.
        interpos_local : `numpy.ndarray` of shape (N, 2)
            phi, z coordiantes (in the local frame) for one of the intersection
            points. If both intersection points are required, reset
            ``self.inner`` and call this function again.


Parametric description of the tube.

        This is just another way to obtain the shape of the tube, e.g.
        for visualization.

        Parameters
        ----------
        phi : np.array
            ``phi`` is the angle around the tube profile. Set to ``None`` to
            use the extend of the element itself.
        z : np.array
            The coordiantes along the radius coordinate. Set to ``None`` to use
            the extend of the element itself.

        Returns
        -------
        xyzw : np.array
            Ring coordinates in global homogeneous coordinate system.
            The array has the following shape (npoints, 2, 4).
            `xyzw[:, 0, :]` describes one rim of the cylinder, `xyzw[:, 1, :]`
            the other rim.


Rotate x-axis to e1, use efix to break rotation ambiguity.

    This function calculates the rotation matrix that rotates the x-axis to
    ``e1``, i.e. it rotates the normal of the y,z-plane to a new plane where
    ``e1`` is the normal. This still leaves the rotation angle of the plane
    free. This function breaks the degeneracy by keeping a vector that is
    coplanar with the normal and fix coplanar with fix and the new normal.

    The purpose of this function is best explained by an example:
    Imagine a plane in the y, z plane. We want to rotate the plane, such that ``e1``
    will be a new normal of the plane. However, since the orientation of the plane
    is not fixed, there is an infinite number of ways to do this.
    This ambiguity can be broken if we additionally require that the rotated
    :math:`\hat e_y` will be coplanar with ``fix``.

    Inspired by the algorithm described in:
    https://www.fastgraph.com/makegames/3drotation/

    Parameters
    ----------
    e1 : np.array of shape (3, )
        new normal of plane
    efix : np.array of shape (3, )
        Vector to break rotational ambiguity.

    Returns
    -------
    rot : np.array of shape (3, 3)
        Rotation matrix


 Rotation matrix for rotation angle `angle` around `axis`

    This is a vectorized version of the routine of the same name in
    ``transforms3d``.

    Parameters
    ----------
    axes : np.array of shape (N, 3)
       vector specifying axis for rotation.
    angle : np.array
       angle of rotation in radians.
    is_normalized : bool, optional
       True if `axis` is already normalized (has norm of 1).  Default False.

    Returns
    -------
    mat : array shape (N, 3,3)
       rotation matrices for specified rotation

    Notes
    -----
    From: http://en.wikipedia.org/wiki/Rotation_matrix#Axis_and_angle


Convert Euclidean coordinates to homogeneous coordinates

    Parameters
    ----------
    e : np.array
        Input Euclidean coordinates. This can be multidimensional, but the last
        dimension must be of size 3.
    w : float
        ``0`` for directions (points at infinity)
        ``1`` for positions (points in Euclidean space)

    Returns
    -------
    h : np.array
        Homogeneous coordinates. Same shape as ``e`` except that the last
        dimension is now has 4 elements.


Convert homogeneous coordinates to Euclidean coordinates

    This functions works for points at infinity and for points in real
    euclidean space, but it expects that each time it is called ``h`` contains
    only the one or the other but not a mixture of both.

    Parameters
    ----------
    h : np.array
        Input homogeneous coordinates. This can be multidimensional, but the
        last dimension must be of size 4.

    Returns
    -------
    e : np.array
        Euclidean coordinates. Same shape as ``e`` except that the last
        last dimension is now has 3 elements.


Calculate the Eukledian distance between 2 points

    Parameters
    ----------
    h_p1, h_p2 : np.ndarray of shape (4) or (N, 4)
        homogeneous coordiantes of input points

    Returns
    -------
    d : np.array of shape (1) or (N)
        Eukledian distance between those points


Transform 3-d translation vector to affine 4*4 matrix.

    Parameters
    ----------
    vec : (3, ) array
        x, y, z translation vector

    Returns
    -------
    m : (4, 4) array
        affine transformation matrix


Transform zoom to affine 4*4 matrix.

    Parameters
    ----------
    vec : float or (3, ) array
        zoom factor for x, y, z dimension (the same zoom is applied to
        every dimension is the input is a scalar).

    Returns
    -------
    m : (4, 4) array
        affine transformation matrix


Transform 3*3 matrix (e.g. rotation) to affine 4*4 matrix.

    Parameters
    ----------
    mat : (3, 3) array
        input matrix

    Returns
    -------
    m : (4, 4) array
        affine transformation matrix


Normalize euklidean vectors.

    Parameters
    ----------
    vec : np.array
        Input vectors of shape (n, 3)

    Returns
    -------
    vec : np.array
        Normalized vectors


Angle range covered by phi, accounting for 2 pi properly

    Parameters
    ----------
    phi : list of two float
        Two angles in radian


Test if an angle is between two others

    Since angles are cyclic, a simple numerical comparison is not
    sufficient, e.g. 355 deg is in the interval [350 deg, 10 deg] even though
    numerically 355 is not less then 10.

    Parameters
    ----------
    angle : float or np.array
        Angle array (in rad)
    border1 : float
        Lower border of interval (inclusive) in radian
    border2 : float
        Higher border of interval (inclusive) in radian

    Returns
    -------
    comparison : bool of same shape as ``angle``
        Result of the comparison

    Examples
    --------

    >>> from marxs.math.utils import angle_between
    >>> angle_between(-0.1, -0.2, 6)
    True


Converts polarization angles to vectors in the direction of polarization.

    Follows convention: Vector perpendicular to photon direction and closest to +y axis is
    angle 0 for polarization direction, unless photon direction is parallel to the y axis,
    in which case the vector closest to the +x axis is angle 0.

    Parameters
    ----------
    dir_array : nx4 np.array
        each row is the homogeneous coordinates for a photon's direction vector
    angles : np.array
        1D array with the polarization angles


Reflection of a polarization vector on a non-polarizing surface.

    This can also be used for other elements that change the direction of the
    photon without adding any more polarization and where both sides
    propagate in the same medium.
    See `Yun (2011) <http://hdl.handle.net/10150/202979>`_, eqn 4.3.13 for details.

    Parameters
    ----------
    delta_dir : np.array of shape (n, 4)
        Array of photon direction coordinates in homogeneous coordinates:
        ``delta_dir = photon['dir_old'] - photons['dir_new']``.
        Note that this vector is **not** normalized.

    Returns
    -------
    q : np.array of shape (n, 4, 4)
        Array of parallel transport ray tracing matrices.


Calculate parallel transport ray tracing matrix.

    Parallel transport for a vector implies that the component s
    (perpendicular, from German *senkrecht*) to the planes spanned by
    ``dir1`` and ``dir2`` stays the same. If ``dir1`` is parallel to ``dir2``
    this plane is not well defined and the resulting matrix elements will
    be set to ``np.nan``, unless ``replace_nans`` is set.

    Note that the ray matrix returned works on an eukledian 3d vector, not a
    homogeneous vector. (Polarization is a vector, thus the forth element of the
    homogeneous vector is always 0 and returning (4,4) matrices is just a waste
    of space.)

    Parameters
    ----------
    dir1, dir2 : np.array of shape (n, 3)
        Direction before and after the interaction.
    jones : np.array of shape (2,2)
        Jones matrix in the local s,p system of the optical element.
    replace_nans : bool
        If ``True`` return an identity matrix for those rays with
        ``dir1=dir2``. In those cases, the local coordinate system is not well
        defined and thus no Jones matrix can be applied. In MARXS ``dir1=dir2``
        often happens if some photons in a list miss the optical element in
        question - these photons just pass through and their polarization vector
        should be unchanged.

    Returns
    -------
    p_mat : np.array of shape(n, 3, 3)


Parallel transport of the polarization vector with no polarization happening.

    Parameters
    ----------
    dir_old, dir_new : np.array of shape (n, 4)
        Old and new photon direction in homogeneous coordinates.
    pol_old : np.array of shape (n, 4)
        Old polarization vector in homogeneous coordinates.
    kwargs : dict
        All other arguments are passed on to `~marxs.math.polarization.paralleltransport_matrix`.
    Returns
    -------
    pol : np.array of shape (m, 4)
        Parallel transported vectors.


Compute angle between normal to plane and line

    Parameters
    ----------
    p_line : array-like
        Line(s) in Pluecker coordinates
    h_plane : array-like
        Plane in homogeneous coordinates

    Returns
    -------
    angle : array-like


Return a line in Pluecker coordinates connecting two points.

    p1, p2 are distinct point on line, direction is p1 -> p2

    Parameters
    ----------
    e_p1, e_p2: array_like
        Array of Eucledian positions

    Returns
    -------
    p_line : array-like
        Lines in Pluecker coordinates


Return a line in Pluecker coordinates connecting two points.

    p1, p2 are distinct point on line, direction is p1 -> p2

    Parameters
    ----------
    e_dir: array_like
        Array of Euclidean direction vectors
    e_point : array-like
        Array of points in eukledian coordinates

    Returns
    -------
    p_line : array-like
        Lines in Pluecker coordinates


Intersect a line in Pluecker coordinates with a plane in homogeneous coordinates.

    This function returns the position of the intersection.
    If the line is parallel (but not identical) to the plane that intersection
    happens "at infinity" and thus the last component of the intersection
    coordinate is 0. If the line is in the plane, then all components of the
    return vector are 0.

    Parameters
    ----------
    p_line : np.array (last dimension has 6 elements)
        Pluecker coordinates of a line or an array of lines
    h_plane : np.array with ``shape=(4, )``
        Homogeneous coordinates of a plane.

    Returns
    -------
    h_point : np.array
        Homogeneous coordinates of the intersection points of line and plane.
        Has the same shape as ``p_line``, but the last dimension has only 4
        elements.


Return a plane in homogeneous coordinates

    Parameters
    ----------
    h_point : array-like
        Point in plane in homogeneous coordinates
    h_dir: array_like
        Normal vector of plane in homogeneous coordinates

    Returns
    -------
    p_line : array-like
        Lines in Pluecker coordinates


Return True is a matrix is orthonormal


Run simple simulation at fixed energy and fixed pointing.

    Parameters
    ----------
    instrument : `marxs.simulator.simulator.Sequence`
        Definition of an instrument with an aperture, but no pointing.
        This function will add a simple fixed pointing and a point source.
    energy : quantity
        Energy for the simulation
    n_photon : int
        number of photons for the simulation
    reference_transform : np.array of shape (4, 4)
        For instruments that do not use the canonical x-axis as the optical axis.
        See `marxs.source.pointing.FixedPointing`

    Returns
    -------
    photons : `astropy.table.Table`
        photon list


Calculate the resolving power in every grating order.

    As input this function takes a Grating Array Structure (``gratings``) and
    a list of photons ready to hit the grating, i.e. the photons have already
    passed through aperture and mirror in e.g. a Chandra-like design. The
    function will take the same input photons and send them through the gas
    looping over the grating orders. In each step of the loop all photons are
    send to the same order, thus the statistical uncertainty on the measured
    spectral resolving power (calculated as ``pos_x / FWHM_x``) is the same
    for every order and is set by the number of photons in the input list.

    As a side effect, the function that selects the grating orders for
    diffraction in ``gas`` will be changed. Pass a deep copy of the GAS if
    this could affect consecutive computations.

    Unlike `~marxs.analysis.gratings.resolvingpower_from_photonlist` this
    function ray-traces the input photon list from the gratings to the
    detectors.

    Parameters
    ----------
    gratings : `marxs.simulator.Parallel`
        Structure that holds individual grating elements,
        e.g. `marxs.design.GratingArrayStructure`
    photons : `astropy.table.Table`
        Photon list to be processed for every order
    orders : np.array of type int
        Order numbers
    detector : marxs optical element or `marxs.design.RowlandTorus` or
        ``None``. Photons are projected onto a detector. There are three ways
        to define this detector:

        - Pass in an instance of an optical element (e.g. a
          `marxs.optics.FlatDetector`).
        - Pass in a `marxs.design.RowlandTorus`. This function will generate
          a detector that follows the Rowland circle.
        - ``None``. A flat detector in the yz plane is used, but the x position
          for this detector is numerically optimized in each step.

    colname : string
        Name of the column that labels the dispersion direction. This is only
        used if a detector instance is passed in explicitly.

    Returns
    -------
    res : np.array
        Resolution defined as FWHM / position for each order.
    fwhm : np.array
        FWHM for each order
    info : dict
        Dictionary with more information, e.g. fit results


Summarize a per-order table of a quantity such as spectral resolution.

    `marxs.analysis.fwhm_per_order` produces a set of data for each grating
    order, most notably the spectral resolution achieved in every spectral
    order for every energy. In practice, however, most orders see only a very
    small number of photons and will not contribute significantly to the
    observed signal.

    This method provides one way to summarize the data by calculating the
    weighted mean of the resolution for each energy, weighted by the
    probability of photons to be diffracted into that order ( = the expected
    fraction).

    Parameters
    ----------
    data : (N, M) np.array
        Array with data for N orders and M energies, e.g. spectral resolution
    orders : np.array of length N of type int
        Order numbers
    energy : np.array for length M of type float
        Energies for each entry in resolution
    gratingeff : `marxs.optics.gratings.EfficiencyFile`
        Object that holds the probability that a photon on a certain energy is
        diffracted to a specific order.

    Returns
    -------
    weighted_res : np.array of length M
        Weighted resolution for each energy


Average R and Aeff over one dimension e.g. channel or orders

    Aeff is summed over the input, while R is calculated as the
    weighted average, using Aeff as the weight for each order.
    This function works for masked arrays and those with nan's.

    Unlike `weighted_per_order` this function summarizes measured
    quantities, so instead of an expected fraction that will be interpolated
    from some table, this uses the observed effective area as weight.

    Parameters
    ----------
    r : array
        Resolving power
    aeff : array
        Effective area
    axis : `None` or int
        Passed to `np.ma.average`. This can be used if the input arrays
        are multi-dimensional.

    Returns
    -------
    aeff_avg : array or float
        Summed effective area
    res_avg : array or float
        Average resolving power


Calculate the resolving power for several grating orders

    If fewer than 20 photons are detected in a single order, this function
    returns nan values.

    Unlike `~marxs.analysis.gratings.resolvingpower_per_order` this method does
    not run any ray-trace simulations. It just extracts information from a
    photon list that is passed in.

    Parameters
    ----------
    photons : `astropy.table.Table`
        Photon event list
    orders : np.array
        Orders for which the resolving power will be calculated
    col : string
        Column name for the column holding the dispersion coordinate.
    zeropos : float or ``None``
        Value of column `col` where the zeroth order is found. If not given,
        this is calculated (assuming the zeroth order photons are part of the
        event list).
    ordercol : string
        Name of column that lists grating order for each photon

    Returns
    -------
    res : np.array
        resolving power for each order
    pos : np.array
        mean value of ``col`` for each order
    std : np.array
        standard deviation of the distribution of ``col`` for each order


Robustly calculate the resolving power for grating orders

    This function wraps `resolvingpower_from_photonlist` to iterate
    over several columns. It calculates the resolving power R for several
    columns and reports the worst case.

    We often want to determine R from the distribution of photons on a CCD.
    Geometry effects like CCDs that are slightly off the
    Rowland torus make R calculated on the detectors lower than an ideal case.
    However, that R can spike if part of the line hits a
    chip gap. So, we can calculate R for a continuous detector on the
    ideal Rowland circle and report that R in such a case.

    Parameters
    ----------
    lphotons : list of `astropy.table.Table`
        List of Photon event list
    orders : np.array
        Orders for which the resolving power will be calculated
    cols : list
        List of column names for the column holding the dispersion coordinate.
    zeropositions : list
        List of values of column `col` where the zeroth order is found. If not given,
        this is calculated (assuming the zeroth order photons are part of the
        event list).
    ordercol : string
        Name of column that lists grating order for each photon

    Returns
    -------
    res : np.array
        resolving power for each order
    pos : np.array
        mean value of ``col`` for each order
    std : np.array
        standard deviation of the distribution of ``col`` for each order


Calculate the effective area several grating orders

    This is based on the probabilities of the photons in the list, so
    this photons list must already account for all instrument
    components, for example, do not forget to set the probability to 0
    for photons that fall into a chip gap.

    Parameters
    ----------
    photons : `astropy.table.Table`
        Photon event list
    orders : np.array
        Orders for which the resolving power will be calculated
    n_photons : int
        Number of photons originally simulated
    A_geom : quantity
        Geometric area of aperture that was used for photon list.
    ordercol : string
        Name of column that lists grating order for each photon

    Returns
    -------
    aeff : np.array
        effective area for each order


Find which photons are in a sub-aperture of two mirrored sectors

    This function includes mirroring on the 0 line, e.g. if
    max_ang=20 deg and ang_0 = 90 deg, includes photons in two sectors
    from 70-110 deg and 250-290 deg.

    Parameters
    ----------
    angle : array
        Angles (in rad) to be tested
    max_ang : float
        Maximal distance (in rad) from ``ang_0`` that is included in the sub-aperturing
    ang_0 : float
        Center of the sub-aperturing region. Default is sub-aperturing perpendicular
        to the ``angle=0`` line, which is the most common sub-aperturing layout with
        subaperturing perpendicular to the dispersion direction as ``angle=0``.


Filter photon list before calculating the effective area.

        Here: No-op (all photons are used.)

        Parameters
        ----------
        photons : `astropy.table.Table`
            Photon list.

        Returns
        -------
        ind : numpy array
           array of boolean values that can be used to index ``photons`` and select
           a sub-set to be used to calculate the effective area.


Filter photon list before calculating the effective area.

        Only photons with ``photons[aeff_filter_col] >= 0`` are used.

        Parameters
        ----------
        photons : `astropy.table.Table`
            Photon list.

        Returns
        -------
        ind : numpy array
           array of boolean values that can be used to index ``photons`` and select
           a sub-set to be used to calculate the effective area.


Return stddev of sigma-clipped data.

    Parameters
    ----------
    photons : `astropy.table.Table`
        Input photon list.
    colname : string
        Column name of the photon distribution to be minimized.
        The default is set for detectors that look for a grating signal
        (which is dispersed in ``det_y`` direction).
    kwargs : see `astropy.stats.sigma_clipped_stats`
        All keyword arguments are passed to `astropy.stats.sigma_clipped_stats`.


Return average distance from center of det_x, det_y distribution.

    Parameters
    ----------
    photons : `astropy.table.Table`
        Input photon list.


Numerically find the position of best focus.

    This routine places detectors at different positions and
    calculates the width of the photon distribution for each position.
    The detector is moved perpendicular to its plane.
    As a side effect, ``photons`` will be set to the intersection with
    the last try of the detector position.

    Parameters
    ----------
    photons : `astropy.table.Table`
        Input photon list.
    objective_func : function
        Function that accepts a np.array as input and return the width.
    object_func_args : dict
        Dict of any other options for the `objective_func`.
    rotation : np.array of shape (3,3)
        Rotation matrix for the detector. By default the detector is parallel
        to the yz plane of the global coordinate system (see `pos4d`).
    kwargs : see `scipy.optimize.minimize`
        All other keyword argument will be passed to `scipy.optimize.minimize`.
    Returns
    -------
    opt : OptimizeResult
        see `scipy.optimize.minimize_scalar`


Calculate the fraction of photons detected for some integer label

    While written for calculating Aeff per order, this can be used
    with any discrete quantity, e.g. Aeff per CCD.

    Parameters
    ----------
    photons : `astropy.table.Table`
        Photon event list
    labels : np.array
        Numeric (integer) labels that are found in column ``col``. When, e.g.,
        the effective area per order is calculated, this array should contain
        the order numbers for which the calculation will be done.
    col : string
        Column name for the order column.

    Returns
    -------
    prop : np.array
        Probability for a photon in a specific order to be detected.


Get table of facet properties

    Parameters
    ----------
    container : `marxs.simulator.Parallel` instance
        This can be some kind of container, typically a grating array
        on the Rowland torus.
    project_plane : `marxs.analysis.ProjectOntoPlane`
        radius and angle are calculated as projected onto this plane

    Returns
    -------
    facettab : `astropy.table.Table`
        Table with facet properties.
        All output columns are prevaced with "facet_" to avoid a clash with columns likely
        to be found in photon tables. That enables easy merging of facet tables with photon tables
        to add the properties of a facet that the photon passed through into the photon table
        like so:

            >>> from astropy import table
            >>> table.join(photons, facettab, join_type='left')  # doctest: +SKIP


This creates and returns a function that assigns energies to photons randomly
    according to the normal distribution with the given mean and standard deviation.


    The default worker function. Should be replaced
    with the desired mapping function on the first
    call.


Run the PUNCH automated pipeline


Clean replay requests


Run a Dask cluster for the pipeline


Unpack image compression control register value.

    See `SciPacket.COMPRESSION_REG` for details.


Unpack CEB image acquisition register value.

    See `SciPacket.ACQUISITION_REG` for details.


    Convert an ECI quaternion to RA and Dec.

    Args:
        q: A numpy array representing the ECI quaternion (q0, q1, q2, q3).

    Returns:
        ra: Right Ascension in degrees.
        dec: Declination in degrees.


Create the preliminary WCS for punchbowl


Generates a short hash of specified length using MD5 and base64 encoding.


    Group up L1 inputs into MZP clusters that match in time (i.e. occur sequentially in one image cluster).

    Handles the swapped MZP/PZM orders, handles any combination of missing files, and for each observatory returns only
    complete MZP triplets


    For a single observatory, groups up L1 inputs into MZP clusters that match in time (i.e. occur sequentially in one
    image cluster).

    Accepts as input the order of P, Z and M, and handles any combination of missing files


Constructs the filename for this file

        Returns
        -------
        str
            properly formatted PUNCH filename


Constructs the directory the file should be stored in

        Parameters
        ----------
        root : str
            the root directory where the top level PUNCH file hierarchy is

        Returns
        -------
        str
            the place to write the file


Given a list of ready-to-launch flow_ids, this task creates flow runs in Prefect for them.
    These flow runs are automatically marked as scheduled in Prefect and will be picked up by a work queue and
    agent as soon as possible.

    Parameters
    ----------
    session : sqlalchemy.orm.session.Session
        A SQLAlchemy session for database interactions
    flow_ids : List[int]
        A list of flow IDs from the punchpipe database identifying which flows to launch

    Returns
    -------
    A list of responses from Prefect about the flow runs that were created


The main launcher flow for Prefect, responsible for identifying flows, based on priority,
        that are ready to run and creating flow runs for them. It also escalates long-waiting flows' priorities.

    See EM 41 or the internal requirements document for more details

    Returns
    -------
    Nothing


    Implement the core logic of each scheduler flow.

    Parameters
    ----------
    query_ready_files_func
        A function that returns a list of lists, where each of the inner lists is a group of files that will be the
        inputs to one flow
    construct_child_file_info
        A function that generates the child File entries for one group/flow
    construct_child_flow_info
        A function that generates the Flow entry for one group
    pipeline_config
        The config or config path
    update_input_file_state
        Whether to change the state of the input files
    new_input_file_state
        The new state to assign to each input file
    session
        A database Session
    reference_time
        Some time of observation time associated with this scheduling run. The meaning is defined by flow-specific
        functions passed into this function.
    args_dictionary: dict
         Values in this dictionary are passed directly to the
         `query_ready_files_func`, `query_ready_files_func`, and `construct_child_flow_info` functions
    children_are_one_to_one
        By default, for each group of input files, it is assumed that all inputs together produce all the output
        files, and FileRelationships are generated accordingly. In a case where a batch of input files are to be
        processed in one flow, this assumption doesn't hold. When this flag is set to True, it is assumed each input
        file connects to only one output file (at the corresponding position in the list of child File objects).


    Reads a notebook from a file, executes it, then writes the result back to a
    new file. If an error occurs while executing a cell, it stops and outputs a
    traceback.


    Return (model, expt, exptdir, desc) strings for keyname in exptdict.keys()

    Examples:

    (model, expt, exptdir, desc) = model_expt_exptdir_desc('1deg')

    for k in exptdict.keys():
        (model, expt, exptdir, desc) = model_expt_exptdir_desc(k)