"""
Waveform pseudo-type functions.

[[[cog
import os, sys
if sys.hexversion < 0x03000000:
    import __builtin__
else:
    import builtins as __builtin__
sys.path.append(os.environ['TRACER_DIR'])
import trace_ex_eng_wave_functions
exobj_eng = trace_ex_eng_wave_functions.trace_module(no_print=True)
]]]
[[[end]]]
"""
# wave_functions.py
# Copyright (c) 2013-2019 Pablo Acosta-Serafini
# See LICENSE for details
# pylint: disable=C0103,C0111,E1101,E1111,R0913,W0212

# Standard library imports
import copy
import math
import warnings

# PyPI imports
with warnings.catch_warnings():
    warnings.filterwarnings("ignore", category=RuntimeWarning)
    import numpy as np
import pexdoc.exh
import pexdoc.pcontracts

# Intra-package imports imports
from .functions import remove_extra_delims
from .constants import FP_ATOL, FP_RTOL
from .wave_core import _interp_dep_vector, Waveform

###
# Functions
###
def _barange(bmin, bmax, inc):
    vector = np.arange(bmin, bmax + inc, inc)
    vector = vector if np.isclose(bmax, vector[-1], FP_RTOL, FP_ATOL) else vector[:-1]
    return vector

def _bound_waveform(wave, indep_min, indep_max):
    """Add independent variable vector bounds if they are not in vector."""
    indep_min, indep_max = _validate_min_max(wave, indep_min, indep_max)
    indep_vector = copy.copy(wave._indep_vector)
    if (
        isinstance(indep_min, float) or isinstance(indep_max, float)
    ) and indep_vector.dtype.name.startswith("int"):
        indep_vector = indep_vector.astype(float)
    min_pos = np.searchsorted(indep_vector, indep_min)
    if not np.isclose(indep_min, indep_vector[min_pos], FP_RTOL, FP_ATOL):
        indep_vector = np.insert(indep_vector, min_pos, indep_min)
    max_pos = np.searchsorted(indep_vector, indep_max)
    if not np.isclose(indep_max, indep_vector[max_pos], FP_RTOL, FP_ATOL):
        indep_vector = np.insert(indep_vector, max_pos, indep_max)
    dep_vector = _interp_dep_vector(wave, indep_vector)
    wave._indep_vector = indep_vector[min_pos : max_pos + 1]
    wave._dep_vector = dep_vector[min_pos : max_pos + 1]

def _build_units(indep_units, dep_units, op):
    """Build unit math operations."""
    if (not dep_units) and (not indep_units):
        return ""
    if dep_units and (not indep_units):
        return dep_units
    if (not dep_units) and indep_units:
        return (
            remove_extra_delims("1{0}({1})".format(op, indep_units))
            if op == "/"
            else remove_extra_delims("({0})".format(indep_units))
        )
    return remove_extra_delims("({0}){1}({2})".format(dep_units, op, indep_units))

def _operation(wave, desc, units, fpointer):
    """Perform generic operation on a waveform object."""
    ret = copy.copy(wave)
    ret.dep_units = units
    ret.dep_name = "{0}({1})".format(desc, ret.dep_name)
    ret._dep_vector = fpointer(ret._dep_vector)
    return ret

def _running_area(indep_vector, dep_vector):
    """Calculate running area under curve."""
    rect_height = np.minimum(dep_vector[:-1], dep_vector[1:])
    rect_base = np.diff(indep_vector)
    rect_area = np.multiply(rect_height, rect_base)
    triang_height = np.abs(np.diff(dep_vector))
    triang_area = 0.5 * np.multiply(triang_height, rect_base)
    return np.cumsum(np.concatenate((np.array([0.0]), triang_area + rect_area)))

def _validate_min_max(wave, indep_min, indep_max):
    """Validate min and max bounds are within waveform's independent variable vector."""
    imin, imax = False, False
    if indep_min is None:
        indep_min = wave._indep_vector[0]
        imin = True
    if indep_max is None:
        indep_max = wave._indep_vector[-1]
        imax = True
    if imin and imax:
        return indep_min, indep_max
    exminmax = pexdoc.exh.addex(
        RuntimeError, "Incongruent `indep_min` and `indep_max` arguments"
    )
    exmin = pexdoc.exh.addai("indep_min")
    exmax = pexdoc.exh.addai("indep_max")
    exminmax(bool(indep_min >= indep_max))
    exmin(
        bool(
            (indep_min < wave._indep_vector[0])
            and (not np.isclose(indep_min, wave._indep_vector[0], FP_RTOL, FP_ATOL))
        )
    )
    exmax(
        bool(
            (indep_max > wave._indep_vector[-1])
            and (not np.isclose(indep_max, wave._indep_vector[-1], FP_RTOL, FP_ATOL))
        )
    )
    return indep_min, indep_max

@pexdoc.pcontracts.contract(wave=Waveform)
def acos(wave):
    r"""
    Return the arc cosine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.acos

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Math domain error)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        ValueError,
        "Math domain error",
        bool((min(wave._dep_vector) < -1) or (max(wave._dep_vector) > 1)),
    )
    return _operation(wave, "acos", "rad", np.arccos)

@pexdoc.pcontracts.contract(wave=Waveform)
def acosh(wave):
    r"""
    Return the hyperbolic arc cosine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.acosh

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Math domain error)

    .. [[[end]]]
    """
    pexdoc.exh.addex(ValueError, "Math domain error", bool(min(wave._dep_vector) < 1))
    return _operation(wave, "acosh", "", np.arccosh)

@pexdoc.pcontracts.contract(wave=Waveform)
def asin(wave):
    r"""
    Return the arc sine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.asin

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Math domain error)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        ValueError,
        "Math domain error",
        bool((min(wave._dep_vector) < -1) or (max(wave._dep_vector) > 1)),
    )
    return _operation(wave, "asin", "rad", np.arcsin)

@pexdoc.pcontracts.contract(wave=Waveform)
def asinh(wave):
    r"""
    Return the hyperbolic arc sine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.asinh

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "asinh", "", np.arcsinh)

@pexdoc.pcontracts.contract(wave=Waveform)
def atan(wave):
    r"""
    Return the arc tangent of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.atan

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "atan", "rad", np.arctan)

@pexdoc.pcontracts.contract(wave=Waveform)
def atanh(wave):
    r"""
    Return the hyperbolic arc tangent of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.atanh

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Math domain error)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        ValueError,
        "Math domain error",
        bool((min(wave._dep_vector) < -1) or (max(wave._dep_vector) > 1)),
    )
    return _operation(wave, "atanh", "", np.arctanh)

@pexdoc.pcontracts.contract(
    wave=Waveform, indep_min="None|number", indep_max="None|number"
)
def average(wave, indep_min=None, indep_max=None):
    r"""
    Return the running average of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.average

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    area = _running_area(ret._indep_vector, ret._dep_vector)
    area[0] = ret._dep_vector[0]
    deltas = ret._indep_vector - ret._indep_vector[0]
    deltas[0] = 1.0
    ret._dep_vector = np.divide(area, deltas)
    ret.dep_name = "average({0})".format(ret._dep_name)
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def ceil(wave):
    r"""
    Return the ceiling of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.ceil

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "ceil", wave.dep_units, np.ceil)

@pexdoc.pcontracts.contract(wave=Waveform)
def cos(wave):
    r"""
    Return the cosine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.wave_functions.cos

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "cos", "", np.cos)

@pexdoc.pcontracts.contract(wave=Waveform)
def cosh(wave):
    r"""
    Return the hyperbolic cosine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.cosh

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "cosh", "", np.cosh)

@pexdoc.pcontracts.contract(wave=Waveform)
def db(wave):
    r"""
    Return a waveform's dependent variable vector expressed in decibels.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.wave_functions.db

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Math domain error)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        ValueError, "Math domain error", bool((np.min(np.abs(wave._dep_vector)) <= 0))
    )
    ret = copy.copy(wave)
    ret.dep_units = "dB"
    ret.dep_name = "db({0})".format(ret.dep_name)
    ret._dep_vector = 20.0 * np.log10(np.abs(ret._dep_vector))
    return ret

@pexdoc.pcontracts.contract(
    wave=Waveform, indep_min="None|number", indep_max="None|number"
)
def derivative(wave, indep_min=None, indep_max=None):
    r"""
    Return the numerical derivative of a waveform's dependent variable vector.

    The method used is the `backwards differences
    <https://en.wikipedia.org/wiki/
    Finite_difference#Forward.2C_backward.2C_and_central_differences>`_ method

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: float

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.derivative

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    delta_indep = np.diff(ret._indep_vector)
    delta_dep = np.diff(ret._dep_vector)
    delta_indep = np.concatenate((np.array([delta_indep[0]]), delta_indep))
    delta_dep = np.concatenate((np.array([delta_dep[0]]), delta_dep))
    ret._dep_vector = np.divide(delta_dep, delta_indep)
    ret.dep_name = "derivative({0})".format(ret._dep_name)
    ret.dep_units = _build_units(ret.indep_units, ret.dep_units, "/")
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def exp(wave):
    r"""
    Return the natural exponent of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.wave_functions.exp

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "exp", "", np.exp)

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def fft(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for peng.wave_functions.fft

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform sampling)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    npoints = npoints or ret._indep_vector.size
    fs = (npoints - 1) / float(ret._indep_vector[-1])
    spoints = min(ret._indep_vector.size, npoints)
    sdiff = np.diff(ret._indep_vector[:spoints])
    cond = not np.all(
        np.isclose(sdiff, sdiff[0] * np.ones(spoints - 1), FP_RTOL, FP_ATOL)
    )
    pexdoc.addex(RuntimeError, "Non-uniform sampling", cond)
    finc = fs / float(npoints - 1)
    indep_vector = _barange(-fs / 2.0, +fs / 2.0, finc)
    dep_vector = np.fft.fft(ret._dep_vector, npoints)
    return Waveform(
        indep_vector=indep_vector,
        dep_vector=dep_vector,
        dep_name="fft({0})".format(ret.dep_name),
        indep_scale="LINEAR",
        dep_scale="LINEAR",
        indep_units="Hz",
        dep_units="",
    )

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def fftdb(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the Fast Fourier Transform of a waveform.

    The dependent variable vector of the returned waveform is expressed in decibels

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.fftdb

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform sampling)

    .. [[[end]]]
    """
    return db(fft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def ffti(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the imaginary part of the Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.ffti

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform sampling)

    .. [[[end]]]
    """
    return imag(fft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def fftm(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the magnitude of the Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.fftm

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform sampling)

    .. [[[end]]]
    """
    return abs(fft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
    unwrap=bool,
    rad=bool,
)
def fftp(wave, npoints=None, indep_min=None, indep_max=None, unwrap=True, rad=True):
    r"""
    Return the phase of the Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :param unwrap: Flag that indicates whether phase should change phase shifts
                   to their :code:`2*pi` complement (True) or not (False)
    :type  unwrap: boolean

    :param rad: Flag that indicates whether phase should be returned in radians
                (True) or degrees (False)
    :type  rad: boolean

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.fftp

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`rad\` is not valid)

     * RuntimeError (Argument \`unwrap\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform sampling)

    .. [[[end]]]
    """
    return phase(fft(wave, npoints, indep_min, indep_max), unwrap=unwrap, rad=rad)

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def fftr(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the real part of the Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.fftr

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform sampling)

    .. [[[end]]]
    """
    return real(fft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(
    wave=Waveform,
    dep_var="number",
    der="None|(int,>=-1,<=+1)",
    inst="int,>0",
    indep_min="None|number",
    indep_max="None|number",
)
def find(wave, dep_var, der=None, inst=1, indep_min=None, indep_max=None):
    r"""
    Return the independent variable point associated with a dependent variable point.

    If the dependent variable point is not in the dependent variable vector the
    independent variable vector point is obtained by linear interpolation

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param dep_var: Dependent vector value to search for
    :type  dep_var: integer, float or complex

    :param der: Dependent vector derivative filter. If +1 only independent
                vector points that have positive derivatives when crossing
                the requested dependent vector point are returned; if -1 only
                independent vector points that have negative derivatives when
                crossing the requested dependent vector point are returned;
                if 0 only independent vector points that have null derivatives
                when crossing the requested dependent vector point are
                returned; otherwise if None all independent vector points are
                returned regardless of the dependent vector derivative. The
                derivative of the first and last point of the waveform is
                assumed to be null
    :type  der: integer, float or complex

    :param inst: Instance number filter. If, for example, **inst** equals 3,
                 then the independent variable vector point at which the
                 dependent variable vector equals the requested value for the
                 third time is returned
    :type  inst: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: integer, float or None if the dependent variable point is not found

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.find

    :raises:
     * RuntimeError (Argument \`dep_var\` is not valid)

     * RuntimeError (Argument \`der\` is not valid)

     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`inst\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    # pylint: disable=C0325,R0914,W0613
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    close_min = np.isclose(min(ret._dep_vector), dep_var, FP_RTOL, FP_ATOL)
    close_max = np.isclose(max(ret._dep_vector), dep_var, FP_RTOL, FP_ATOL)
    if ((np.amin(ret._dep_vector) > dep_var) and (not close_min)) or (
        (np.amax(ret._dep_vector) < dep_var) and (not close_max)
    ):
        return None
    cross_wave = ret._dep_vector - dep_var
    sign_wave = np.sign(cross_wave)
    exact_idx = np.where(np.isclose(ret._dep_vector, dep_var, FP_RTOL, FP_ATOL))[0]
    # Locations where dep_vector crosses dep_var or it is equal to it
    left_idx = np.where(np.diff(sign_wave))[0]
    # Remove elements to the left of exact matches
    left_idx = np.setdiff1d(left_idx, exact_idx)
    left_idx = np.setdiff1d(left_idx, exact_idx - 1)
    right_idx = left_idx + 1 if left_idx.size else np.array([])
    indep_var = ret._indep_vector[exact_idx] if exact_idx.size else np.array([])
    dvector = np.zeros(exact_idx.size).astype(int) if exact_idx.size else np.array([])
    if left_idx.size and (ret.interp == "STAIRCASE"):
        idvector = (
            2.0 * (ret._dep_vector[right_idx] > ret._dep_vector[left_idx]).astype(int)
            - 1
        )
        if indep_var.size:
            indep_var = np.concatenate((indep_var, ret._indep_vector[right_idx]))
            dvector = np.concatenate((dvector, idvector))
            sidx = np.argsort(indep_var)
            indep_var = indep_var[sidx]
            dvector = dvector[sidx]
        else:
            indep_var = ret._indep_vector[right_idx]
            dvector = idvector
    elif left_idx.size:
        y_left = ret._dep_vector[left_idx]
        y_right = ret._dep_vector[right_idx]
        x_left = ret._indep_vector[left_idx]
        x_right = ret._indep_vector[right_idx]
        slope = ((y_left - y_right) / (x_left - x_right)).astype(float)
        # y = y0+slope*(x-x0) => x0+(y-y0)/slope
        if indep_var.size:
            indep_var = np.concatenate(
                (indep_var, x_left + ((dep_var - y_left) / slope))
            )
            dvector = np.concatenate((dvector, np.where(slope > 0, 1, -1)))
            sidx = np.argsort(indep_var)
            indep_var = indep_var[sidx]
            dvector = dvector[sidx]
        else:
            indep_var = x_left + ((dep_var - y_left) / slope)
            dvector = np.where(slope > 0, +1, -1)
    if der is not None:
        indep_var = np.extract(dvector == der, indep_var)
    return indep_var[inst - 1] if inst <= indep_var.size else None

@pexdoc.pcontracts.contract(wave=Waveform)
def floor(wave):
    r"""
    Return the floor of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.floor

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "floor", wave.dep_units, np.floor)

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def ifft(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the inverse Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.ifft

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform frequency spacing)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    npoints = npoints or ret._indep_vector.size
    spoints = min(ret._indep_vector.size, npoints)
    sdiff = np.diff(ret._indep_vector[:spoints])
    finc = sdiff[0]
    cond = not np.all(np.isclose(sdiff, finc * np.ones(spoints - 1), FP_RTOL, FP_ATOL))
    pexdoc.addex(RuntimeError, "Non-uniform frequency spacing", cond)
    fs = (npoints - 1) * finc
    tinc = 1 / float(fs)
    tend = 1 / float(finc)
    indep_vector = _barange(0, tend, tinc)
    dep_vector = np.fft.ifft(ret._dep_vector, npoints)
    return Waveform(
        indep_vector=indep_vector,
        dep_vector=dep_vector,
        dep_name="ifft({0})".format(ret.dep_name),
        indep_scale="LINEAR",
        dep_scale="LINEAR",
        indep_units="sec",
        dep_units="",
    )

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def ifftdb(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the inverse Fast Fourier Transform of a waveform.

    The dependent variable vector of the returned waveform is expressed in decibels

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.ifftdb

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform frequency spacing)

    .. [[[end]]]
    """
    return db(ifft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def iffti(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the imaginary part of the inverse Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.iffti

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform frequency spacing)

    .. [[[end]]]
    """
    return imag(ifft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def ifftm(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the magnitude of the inverse Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.ifftm

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform frequency spacing)

    .. [[[end]]]
    """
    return abs(ifft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
    unwrap=bool,
    rad=bool,
)
def ifftp(wave, npoints=None, indep_min=None, indep_max=None, unwrap=True, rad=True):
    r"""
    Return the phase of the inverse Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :param unwrap: Flag that indicates whether phase should change phase shifts
                   to their :code:`2*pi` complement (True) or not (False)
    :type  unwrap: boolean

    :param rad: Flag that indicates whether phase should be returned in radians
                (True) or degrees (False)
    :type  rad: boolean

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.ifftp

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`rad\` is not valid)

     * RuntimeError (Argument \`unwrap\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform frequency spacing)

    .. [[[end]]]
    """
    return phase(ifft(wave, npoints, indep_min, indep_max), unwrap=unwrap, rad=rad)

@pexdoc.pcontracts.contract(
    wave=Waveform,
    npoints="None|(int,>=1)",
    indep_min="None|number",
    indep_max="None|number",
)
def ifftr(wave, npoints=None, indep_min=None, indep_max=None):
    r"""
    Return the real part of the inverse Fast Fourier Transform of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param npoints: Number of points to use in the transform. If **npoints**
                    is less than the size of the independent variable vector
                    the waveform is truncated; if **npoints** is greater than
                    the size of the independent variable vector, the waveform
                    is zero-padded
    :type  npoints: positive integer

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.ifftr

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`npoints\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

     * RuntimeError (Non-uniform frequency spacing)

    .. [[[end]]]
    """
    return real(ifft(wave, npoints, indep_min, indep_max))

@pexdoc.pcontracts.contract(wave=Waveform)
def imag(wave):
    r"""
    Return the imaginary part of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.imag

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "imag", wave.dep_units, np.imag)

@pexdoc.pcontracts.contract(
    wave=Waveform, indep_min="None|number", indep_max="None|number"
)
def integral(wave, indep_min=None, indep_max=None):
    r"""
    Return the running integral of a waveform's dependent variable vector.

    The method used is the `trapezoidal
    <https://en.wikipedia.org/wiki/Trapezoidal_rule>`_ method

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.integral

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    ret._dep_vector = _running_area(ret._indep_vector, ret._dep_vector)
    ret.dep_name = "integral({0})".format(ret._dep_name)
    ret.dep_units = _build_units(ret.indep_units, ret.dep_units, "*")
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def group_delay(wave):
    r"""
    Return the group delay of a waveform.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.group_delay

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    ret = -derivative(phase(wave, unwrap=True) / (2 * math.pi))
    ret.dep_name = "group_delay({0})".format(wave.dep_name)
    ret.dep_units = "sec"
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def log(wave):
    r"""
    Return the natural logarithm of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.wave_functions.log

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Math domain error)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        ValueError, "Math domain error", bool((min(wave._dep_vector) <= 0))
    )
    return _operation(wave, "log", "", np.log)

@pexdoc.pcontracts.contract(wave=Waveform)
def log10(wave):
    r"""
    Return the base 10 logarithm of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.log10

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Math domain error)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        ValueError, "Math domain error", bool((min(wave._dep_vector) <= 0))
    )
    return _operation(wave, "log10", "", np.log10)

@pexdoc.pcontracts.contract(
    wave=Waveform, indep_min="None|number", indep_max="None|number"
)
def naverage(wave, indep_min=None, indep_max=None):
    r"""
    Return the numerical average of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.naverage

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    delta_x = ret._indep_vector[-1] - ret._indep_vector[0]
    return np.trapz(ret._dep_vector, x=ret._indep_vector) / delta_x

@pexdoc.pcontracts.contract(
    wave=Waveform, indep_min="None|number", indep_max="None|number"
)
def nintegral(wave, indep_min=None, indep_max=None):
    r"""
    Return the numerical integral of a waveform's dependent variable vector.

    The method used is the `trapezoidal
    <https://en.wikipedia.org/wiki/Trapezoidal_rule>`_ method

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: float

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.nintegral

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    return np.trapz(ret._dep_vector, ret._indep_vector)

@pexdoc.pcontracts.contract(
    wave=Waveform, indep_min="None|number", indep_max="None|number"
)
def nmax(wave, indep_min=None, indep_max=None):
    r"""
    Return the maximum of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: float

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.nmax

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    return np.max(ret._dep_vector)

@pexdoc.pcontracts.contract(
    wave=Waveform, indep_min="None|number", indep_max="None|number"
)
def nmin(wave, indep_min=None, indep_max=None):
    r"""
    Return the minimum of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :rtype: float

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.nmin

    :raises:
     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    _bound_waveform(ret, indep_min, indep_max)
    return np.min(ret._dep_vector)

@pexdoc.pcontracts.contract(wave=Waveform, unwrap=bool, rad=bool)
def phase(wave, unwrap=True, rad=True):
    r"""
    Return the phase of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param unwrap: Flag that indicates whether phase should change phase shifts
                   to their :code:`2*pi` complement (True) or not (False)
    :type  unwrap: boolean

    :param rad: Flag that indicates whether phase should be returned in radians
                (True) or degrees (False)
    :type  rad: boolean

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.phase

    :raises:
     * RuntimeError (Argument \`rad\` is not valid)

     * RuntimeError (Argument \`unwrap\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    ret.dep_units = "rad" if rad else "deg"
    ret.dep_name = "phase({0})".format(ret.dep_name)
    ret._dep_vector = (
        np.unwrap(np.angle(ret._dep_vector)) if unwrap else np.angle(ret._dep_vector)
    )
    if not rad:
        ret._dep_vector = np.rad2deg(ret._dep_vector)
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def real(wave):
    r"""
    Return the real part of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.real

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "real", wave.dep_units, np.real)

@pexdoc.pcontracts.contract(wave=Waveform, decimals="int,>=0")
def round(wave, decimals=0):
    r"""
    Round a waveform's dependent variable vector to a given number of decimal places.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param decimals: Number of decimals to round to
    :type  decimals: integer

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.round

    :raises:
     * RuntimeError (Argument \`decimals\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    # pylint: disable=W0622
    pexdoc.exh.addex(
        TypeError,
        "Cannot convert complex to integer",
        wave._dep_vector.dtype.name.startswith("complex"),
    )
    ret = copy.copy(wave)
    ret.dep_name = "round({0}, {1})".format(ret.dep_name, decimals)
    ret._dep_vector = np.round(wave._dep_vector, decimals)
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def sin(wave):
    r"""
    Return the sine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.wave_functions.sin

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "sin", "", np.sin)

@pexdoc.pcontracts.contract(wave=Waveform)
def sinh(wave):
    r"""
    Return the hyperbolic sine of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.sinh

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "sinh", "", np.sinh)

@pexdoc.pcontracts.contract(wave=Waveform)
def sqrt(wave):
    r"""
    Return the square root of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.sqrt

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    dep_units = "{0}**0.5".format(wave.dep_units)
    return _operation(wave, "sqrt", dep_units, np.sqrt)

@pexdoc.pcontracts.contract(
    wave=Waveform,
    dep_name="str|None",
    indep_min="None|number",
    indep_max="None|number",
    indep_step="None|number",
)
def subwave(wave, dep_name=None, indep_min=None, indep_max=None, indep_step=None):
    r"""
    Return a waveform that is a sub-set of a waveform, potentially re-sampled.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param  dep_name: Independent variable name
    :type   dep_name: `NonNullString <https://pexdoc.readthedocs.io/en/stable/
                        ptypes.html#nonnullstring>`_

    :param indep_min: Independent vector start point of computation
    :type  indep_min: integer or float

    :param indep_max: Independent vector stop point of computation
    :type  indep_max: integer or float

    :param indep_step: Independent vector step
    :type  indep_step: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc(raised=True)) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.subwave

    :raises:
     * RuntimeError (Argument \`dep_name\` is not valid)

     * RuntimeError (Argument \`indep_max\` is not valid)

     * RuntimeError (Argument \`indep_min\` is not valid)

     * RuntimeError (Argument \`indep_step\` is greater than independent
       vector range)

     * RuntimeError (Argument \`indep_step\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * RuntimeError (Incongruent \`indep_min\` and \`indep_max\`
       arguments)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    if dep_name is not None:
        ret.dep_name = dep_name
    _bound_waveform(ret, indep_min, indep_max)
    pexdoc.addai("indep_step", bool((indep_step is not None) and (indep_step <= 0)))
    exmsg = "Argument `indep_step` is greater than independent vector range"
    cond = bool(
        (indep_step is not None)
        and (indep_step > ret._indep_vector[-1] - ret._indep_vector[0])
    )
    pexdoc.addex(RuntimeError, exmsg, cond)
    if indep_step:
        indep_vector = _barange(indep_min, indep_max, indep_step)
        dep_vector = _interp_dep_vector(ret, indep_vector)
        ret._set_indep_vector(indep_vector, check=False)
        ret._set_dep_vector(dep_vector, check=False)
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def tan(wave):
    r"""
    Return the tangent of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.wave_functions.tan

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "tan", "", np.tan)

@pexdoc.pcontracts.contract(wave=Waveform)
def tanh(wave):
    r"""
    Return the hyperbolic tangent of a waveform's dependent variable vector.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.tanh

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    return _operation(wave, "tanh", "", np.tanh)

@pexdoc.pcontracts.contract(wave=Waveform)
def wcomplex(wave):
    r"""
    Convert a waveform's dependent variable vector to complex.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.wcomplex

    :raises: RuntimeError (Argument \`wave\` is not valid)

    .. [[[end]]]
    """
    ret = copy.copy(wave)
    ret._dep_vector = ret._dep_vector.astype(np.complex)
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def wfloat(wave):
    r"""
    Convert a waveform's dependent variable vector to float.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.wfloat

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * TypeError (Cannot convert complex to float)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        TypeError,
        "Cannot convert complex to float",
        wave._dep_vector.dtype.name.startswith("complex"),
    )
    ret = copy.copy(wave)
    ret._dep_vector = ret._dep_vector.astype(np.float)
    return ret

@pexdoc.pcontracts.contract(wave=Waveform)
def wint(wave):
    r"""
    Convert a waveform's dependent variable vector to integer.

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.wint

    :raises:
     * RuntimeError (Argument \`wave\` is not valid)

     * TypeError (Cannot convert complex to integer)

    .. [[[end]]]
    """
    pexdoc.exh.addex(
        TypeError,
        "Cannot convert complex to integer",
        wave._dep_vector.dtype.name.startswith("complex"),
    )
    ret = copy.copy(wave)
    ret._dep_vector = ret._dep_vector.astype(np.int)
    return ret

@pexdoc.pcontracts.contract(wave=Waveform, indep_var="number")
def wvalue(wave, indep_var):
    r"""
    Return the dependent variable value at a given independent variable point.

    If the independent variable point is not in the independent variable vector
    the dependent variable value is obtained by linear interpolation

    :param wave: Waveform
    :type  wave: :py:class:`peng.eng.Waveform`

    :param indep_var: Independent variable point for which the dependent
                      variable is to be obtained
    :type  indep_var: integer or float

    :rtype: :py:class:`peng.eng.Waveform`

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.wave_functions.wvalue

    :raises:
     * RuntimeError (Argument \`indep_var\` is not valid)

     * RuntimeError (Argument \`wave\` is not valid)

     * ValueError (Argument \`indep_var\` is not in the independent
       variable vector range)

    .. [[[end]]]
    """
    close_min = np.isclose(indep_var, wave._indep_vector[0], FP_RTOL, FP_ATOL)
    close_max = np.isclose(indep_var, wave._indep_vector[-1], FP_RTOL, FP_ATOL)
    pexdoc.exh.addex(
        ValueError,
        "Argument `indep_var` is not in the independent variable vector range",
        bool(
            ((indep_var < wave._indep_vector[0]) and (not close_min))
            or ((indep_var > wave._indep_vector[-1]) and (not close_max))
        ),
    )
    if close_min:
        return wave._dep_vector[0]
    if close_max:
        return wave._dep_vector[-1]
    idx = np.searchsorted(wave._indep_vector, indep_var)
    xdelta = wave._indep_vector[idx] - wave._indep_vector[idx - 1]
    ydelta = wave._dep_vector[idx] - wave._dep_vector[idx - 1]
    slope = ydelta / float(xdelta)
    return wave._dep_vector[idx - 1] + slope * (indep_var - wave._indep_vector[idx - 1])


from __future__ import unicode_literals

from django.conf import settings
from django.contrib.staticfiles.finders import BaseStorageFinder

from .storage import JSPMFileStorage

class SystemFinder(BaseStorageFinder):
    """
    A staticfiles finder that looks in STATIC_ROOT for jspm files.

    Inspired by the CompressorFinder from django-compressor.
    """
    storage = JSPMFileStorage

    def find(self, path, all=False):
        """
        Only allow lookups for jspm_packages.

        # TODO: figure out the 'jspm_packages' dir from packag.json.
        """
        bits = path.split('/')
        dirs_to_serve = ['jspm_packages', settings.SYSTEMJS_OUTPUT_DIR]
        if not bits or bits[0] not in dirs_to_serve:
            return []
        return super(SystemFinder, self).find(path, all=all)

    def list(self, ignore_patterns):
        # skip this finder alltogether during collectstatic
        return []


# setup.py
# Copyright (c) 2013-2019 Pablo Acosta-Serafini
# See LICENSE for details
# pylint: disable=C0111,E0401,E0601,E1111,R0904,W0122,W0201,W0621

# Taken in large part from:
#    http://www.jeffknupp.com/blog/2013/08/16/
#    open-sourcing-a-python-project-the-right-way/
# With additional hints from:
#     http://oddbird.net/set-your-code-free-preso/
# The function to get the version number from __init__.py is from:
#     https://python-packaging-user-guide.readthedocs.org/
#     en/latest/single_source_version/
# Standard library imports
from __future__ import print_function
import io
import glob
import os
import sys

# PyPI imports
from setuptools import setup
from setuptools.command.test import test as TestCommand

# Intra-package imports
from pypkg.functions import (
    get_entry_points,
    get_pkg_data_files,
    load_requirements,
    python_version,
)

###
# Supported interpreter check
###
# When installing from tarball/zip/wheel, path is temporary one and setup.py
# is not in a directory where its name is the package name, have to find
# package name by finding location of pkgdata file
STEM = "pkgdata"
FOUND = False
START_DIR = os.path.dirname(os.path.abspath(__file__))
for (DIRPATH, _, FNAMES) in os.walk(START_DIR):
    # Ignore .tox, .git and other directories
    if DIRPATH[len(START_DIR) + 1 :].startswith("."):
        continue
    #
    for FNAME in FNAMES:
        if os.path.splitext(os.path.basename(FNAME))[0] == STEM:
            FNAME = os.path.join(DIRPATH, FNAME)
            sys.path.append(DIRPATH)
            import pkgdata

            PKG_NAME = os.path.basename(
                os.path.dirname(os.path.abspath(sys.modules["pkgdata"].__file__))
            )
            FOUND = True
            break
if not FOUND:
    raise RuntimeError("Supported Python interpreter versions cold not be found")
PYTHON_VER = python_version("{0:0x}".format(sys.hexversion & 0xFFFF0000)[:-4])
SUPPORTED_INTERPS = sorted(pkgdata.SUPPORTED_INTERPS)
if PYTHON_VER not in SUPPORTED_INTERPS:
    sys.exit("Supported interpreter versions: {0}".format(", ".join(SUPPORTED_INTERPS)))

###
# Functions
###
def get_short_desc(long_desc):
    """Get first sentence of first paragraph of long description."""
    found = False
    olines = []
    for line in [item.rstrip() for item in long_desc.split("\n")]:
        if found and (((not line) and (not olines)) or (line and olines)):
            olines.append(line)
        elif found and olines and (not line):
            return (" ".join(olines).split(".")[0]).strip()
        found = line == ".. [[[end]]]" if not found else found
    return ""

def read(*filenames, **kwargs):
    """Read plain text file(s)."""
    encoding = kwargs.get("encoding", "utf-8")
    sep = kwargs.get("sep", "\n")
    buf = []
    for filename in filenames:
        with io.open(filename, encoding=encoding) as fobj:
            buf.append(fobj.read())
    return sep.join(buf)

###
# Global variables
###
REPO = "http://github.com/pmacosta/{pkg_name}/".format(pkg_name=PKG_NAME)
AUTHOR = "Pablo Acosta-Serafini"
AUTHOR_EMAIL = "pmasdev@gmail.com"
LICENSE = "MIT"
PKG_DIR = os.path.abspath(os.path.dirname(__file__))
LONG_DESCRIPTION = read(
    os.path.join(PKG_DIR, "README.rst"), os.path.join(PKG_DIR, "CHANGELOG.rst")
)
SHORT_DESC = get_short_desc(LONG_DESCRIPTION)
# Actual directory is os.join(sys.prefix, 'share', PKG_NAME)
SHARE_DIR = os.path.join("share", PKG_NAME)
INSTALL_REQUIRES = load_requirements(PKG_DIR, PYTHON_VER, "source")
TESTING_REQUIRES = load_requirements(PKG_DIR, PYTHON_VER, "testing")
if os.environ.get("MERGE_REQUIREMENTS", False):
    INSTALL_REQUIRES = INSTALL_REQUIRES + TESTING_REQUIRES
try:
    DATA_FILES = get_pkg_data_files(SHARE_DIR)
except IOError:
    print("PKG_DIR: {0}".format(PKG_DIR))
    print("Contents:")
    print(glob.glob(os.path.join(PKG_DIR, "*")))
    print("PKG_DIR/data")
    print("Contents:")
    print(glob.glob(os.path.join(PKG_DIR, "data", "*")))
    raise

###
# Extract version (from coveragepy)
###
VERSION_PY = os.path.join(PKG_DIR, PKG_NAME, "pkgdata.py")
with open(VERSION_PY) as fobj:
    __version__ = VERSION_INFO = ""
    # Execute the code in pkgdata.py.
    exec(compile(fobj.read(), VERSION_PY, "exec"))
if VERSION_INFO[3] == "alpha":
    DEVSTAT = "3 - Alpha"
elif VERSION_INFO[3] in ["beta", "candidate"]:
    DEVSTAT = "4 - Beta"
else:
    assert VERSION_INFO[3] == "final"
    DEVSTAT = "5 - Production/Stable"

###
# Classes
###
class Tox(TestCommand):  # noqa
    user_options = [("tox-args=", "a", "Arguments to pass to tox")]

    def initialize_options(self):  # noqa
        TestCommand.initialize_options(self)
        self.tox_args = None

    def finalize_options(self):  # noqa
        TestCommand.finalize_options(self)
        self.test_args = []
        self.test_suite = True

    def run_tests(self):  # noqa
        import shlex
        import tox

        args = self.tox_args
        if args:
            args = shlex.split(self.tox_args)
        errno = tox.cmdline(args=args)
        sys.exit(errno)

###
# Processing
###
# package_data is used only for binary packages, i.e.
# $ python setup.py bdist ...
# but NOT when building source packages, i.e.
# $ python setup.py sdist ...
setup(
    name=PKG_NAME,
    version=__version__,
    url=REPO,
    license=LICENSE,
    author=AUTHOR,
    tests_require=TESTING_REQUIRES,
    install_requires=INSTALL_REQUIRES,
    cmdclass={"tests": Tox},
    author_email=AUTHOR_EMAIL,
    description=SHORT_DESC,
    long_description=LONG_DESCRIPTION,
    packages=[PKG_NAME],
    entry_points=get_entry_points(),
    data_files=DATA_FILES,
    zip_safe=False,
    platforms="any",
    classifiers=[
        "Programming Language :: Python :: {0}".format(pyver)
        for pyver in SUPPORTED_INTERPS
    ]
    + [
        "Development Status :: " + DEVSTAT,
        "Natural Language :: English",
        "Environment :: Console",
        "Intended Audience :: Developers",
        "License :: OSI Approved :: " + LICENSE + " License",
        "Operating System :: OS Independent",
        "Topic :: Software Development :: Libraries :: Python Modules",
    ],
)


"""
Mixin for various command to retreive locations of systemjs_import calls.
"""
from __future__ import unicode_literals

import io
import os
import re
from collections import OrderedDict

from django.conf import settings
from django.core.management.base import CommandError
from django.core.management.utils import handle_extensions
from django.template import loader, TemplateDoesNotExist
from django.template.base import TOKEN_BLOCK
from django.template.loaders.app_directories import get_app_template_dirs

from systemjs.compat import Lexer
from systemjs.templatetags.system_tags import SystemImportNode

SYSTEMJS_TAG_RE = re.compile(r"""systemjs_import\s+(['\"])(?P<app>.*)\1""")

RESOLVE_CONTEXT = {}

class TemplateDiscoveryMixin(object):

    def add_arguments(self, parser):
        tpl_group = parser.add_mutually_exclusive_group()
        tpl_group.add_argument(
            '--extension', '-e', dest='extensions',
            help='The file extension(s) to examine (default: "html"). Separate '
                 'multiple extensions with commas, or use -e multiple times.',
            action='append')
        tpl_group.add_argument(
            '--template', '-t', dest='templates',
            help='The templates to examine. Separate multiple template names with'
                 'commas, or use -t multiple times',
            action='append')

        parser.add_argument(
            '--symlinks', '-s', action='store_true', dest='symlinks',
            default=False, help='Follows symlinks to directories when examining '
                                'source code and templates for SystemJS imports.')

        super(TemplateDiscoveryMixin, self).add_arguments(parser)

    def handle(self, **options):
        self.symlinks = options.get('symlinks')
        extensions = options.get('extensions') or ['html']
        self.extensions = handle_extensions(extensions)

    def discover_templates(self):
        template_dirs = list(get_app_template_dirs('templates'))
        for config in settings.TEMPLATES:
            # only support vanilla Django templates
            if config['BACKEND'] != 'django.template.backends.django.DjangoTemplates':
                continue
            template_dirs += list(config['DIRS'])

        all_files = []
        for template_dir in template_dirs:
            for dirpath, dirnames, filenames in os.walk(template_dir, topdown=True, followlinks=self.symlinks):
                for filename in filenames:
                    filepath = os.path.join(dirpath, filename)
                    file_ext = os.path.splitext(filename)[1]
                    if file_ext not in self.extensions:
                        continue
                    template_name = os.path.relpath(filepath, template_dir)
                    all_files.append((template_name, filepath))

        return all_files

    def find_apps(self, templates=None):
        """
        Crawls the (specified) template files and extracts the apps.

        If `templates` is specified, the template loader is used and the template
        is tokenized to extract the SystemImportNode. An empty context is used
        to resolve the node variables.
        """

        all_apps = OrderedDict()

        if not templates:
            all_files = self.discover_templates()
            for tpl_name, fp in all_files:
                # this is the most performant - a testcase that used the loader with tpl_name
                # was about 8x slower for a project with ~5 apps in different templates :(
                with io.open(fp, 'r', encoding=settings.FILE_CHARSET) as template_file:
                    src_data = template_file.read()

                for t in Lexer(src_data).tokenize():
                    if t.token_type == TOKEN_BLOCK:
                        imatch = SYSTEMJS_TAG_RE.match(t.contents)
                        if imatch:
                            all_apps.setdefault(tpl_name, [])
                            all_apps[tpl_name].append(imatch.group('app'))
        else:
            for tpl_name in templates:
                try:
                    template = loader.get_template(tpl_name)
                except TemplateDoesNotExist:
                    raise CommandError('Template \'%s\' does not exist' % tpl_name)
                import_nodes = template.template.nodelist.get_nodes_by_type(SystemImportNode)
                for node in import_nodes:
                    app = node.path.resolve(RESOLVE_CONTEXT)
                    if not app:
                        self.stdout.write(self.style.WARNING(
                            '{tpl}: Could not resolve path with context {ctx}, skipping.'.format(
                                tpl=tpl_name, ctx=RESOLVE_CONTEXT)
                        ))
                        continue
                    all_apps.setdefault(tpl_name, [])
                    all_apps[tpl_name].append(app)

        return all_apps

class BundleOptionsMixin(object):

    def add_arguments(self, parser):
        super(BundleOptionsMixin, self).add_arguments(parser)

        parser.add_argument(
            '--sfx',
            action='store_true', dest='sfx',
            help="Generate self-executing bundles.")

        parser.add_argument('--node-path', help='Path to the project `node_modules` directory')
        parser.add_argument('--minify', action='store_true', help='Let jspm minify the bundle')
        parser.add_argument('--minimal', action='store_true', help='Only (re)bundle if changes detected')
        parser.add_argument('--skip-source-maps', action='store_true', help='Skip source maps generation')

    def get_system_opts(self, options):
        system_options = ['minimal', 'minify', 'sfx', 'skip_source_maps']
        return {opt: options.get(opt) for opt in system_options}


from __future__ import unicode_literals

import posixpath
import re

from django import template
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.forms.utils import flatatt
from django.template.base import token_kwargs

from systemjs.base import System

register = template.Library()

# Regex for token keyword arguments
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")

class SystemImportNode(template.Node):

    def __init__(self, path, tag_attrs=None):
        self.path = path
        self.tag_attrs = tag_attrs

    def render(self, context):
        """
        Build the filepath by appending the extension.
        """
        module_path = self.path.resolve(context)
        if not settings.SYSTEMJS_ENABLED:
            if settings.SYSTEMJS_DEFAULT_JS_EXTENSIONS:
                name, ext = posixpath.splitext(module_path)
                if not ext:
                    module_path = '{}.js'.format(module_path)

            if settings.SYSTEMJS_SERVER_URL:
                tpl = """<script src="{url}{app}" type="text/javascript"></script>"""
            else:
                tpl = """<script type="text/javascript">System.import('{app}');</script>"""
            return tpl.format(app=module_path, url=settings.SYSTEMJS_SERVER_URL)

        # else: create a bundle
        rel_path = System.get_bundle_path(module_path)
        url = staticfiles_storage.url(rel_path)

        tag_attrs = {'type': 'text/javascript'}
        for key, value in self.tag_attrs.items():
            if not isinstance(value, bool):
                value = value.resolve(context)
            tag_attrs[key] = value

        return """<script{attrs} src="{url}"></script>""".format(
            url=url, attrs=flatatt(tag_attrs)
        )

    @classmethod
    def handle_token(cls, parser, token):
        bits = token.split_contents()
        attrs = {}

        if len(bits) < 2:
            raise template.TemplateSyntaxError("'%s' takes at least one argument (js module)" % bits[0])

        if len(bits) > 2:
            for bit in bits[2:]:
                # First we try to extract a potential kwarg from the bit
                kwarg = token_kwargs([bit], parser)
                if kwarg:
                    attrs.update(kwarg)
                else:
                    attrs[bit] = True  # for flatatt

        path = parser.compile_filter(bits[1])
        return cls(path, tag_attrs=attrs)

@register.tag
def systemjs_import(parser, token):
    """
    Import a Javascript module via SystemJS, bundling the app.

    Syntax::

        {% systemjs_import 'path/to/file' %}

    Example::

        {% systemjs_import 'mydjangoapp/js/myapp' %}

    Which would be rendered like::

        <script type="text/javascript" src="/static/CACHE/mydjangoapp.js.min.myapp.js"></script>

    where /static/CACHE can be configured through settings.

    In DEBUG mode, the result would be

        <script type="text/javascript">System.import('mydjangoapp/js/myapp.js');</script>
    """

    return SystemImportNode.handle_token(parser, token)


# ptypes.py
# Copyright (c) 2013-2019 Pablo Acosta-Serafini
# See LICENSE for details
# pylint: disable=C0111,R0912,R0916

# Standard library imports
import math

# PyPI imports
import numpy as np
import pexdoc.pcontracts

###
# Global variables
###
_SUFFIX_TUPLE = (
    "y",
    "z",
    "a",
    "f",
    "p",
    "n",
    "u",
    "m",
    " ",
    "k",
    "M",
    "G",
    "T",
    "P",
    "E",
    "Z",
    "Y",
)

###
# Functions
###
def _check_increasing_real_numpy_vector(obj):
    # pylint: disable=C0103
    if isinstance(obj, np.ndarray) and (len(obj.shape) == 1) and (obj.shape[0] > 0):
        otype = obj.dtype.name
        result = any([otype.startswith(item) for item in ("int", "float")])
        return not (
            bool(result)
            and (
                (obj.shape[0] == 1) or ((obj.shape[0] > 1) and (min(np.diff(obj)) > 0))
            )
        )
    return True

def _check_number_numpy_vector(obj):
    if isinstance(obj, np.ndarray) and (len(obj.shape) == 1) and (obj.shape[0] > 0):
        otype = obj.dtype.name
        result = any([otype.startswith(item) for item in ("int", "float", "complex")])
        return not bool(result)
    return True

def _check_real_numpy_vector(obj):
    if isinstance(obj, np.ndarray) and (len(obj.shape) == 1) and (obj.shape[0] > 0):
        otype = obj.dtype.name
        result = any([otype.startswith(item) for item in ("int", "float")])
        return not bool(result)
    return True

@pexdoc.pcontracts.new_contract()
def engineering_notation_number(obj):
    r"""
    Validate if an object is an :ref:`EngineeringNotationNumber` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    try:
        obj = obj.rstrip()
        float(obj[:-1] if obj[-1] in _SUFFIX_TUPLE else obj)
        return None
    except (AttributeError, IndexError, ValueError):
        # AttributeError: obj.rstrip(), object could not be a string
        # IndexError: obj[-1], when an empty string
        # ValueError: float(), when not a string representing a number
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract()
def engineering_notation_suffix(obj):
    r"""
    Validate if an object is an :ref:`EngineeringNotationSuffix` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the *name* of the argument the
     contract is attached to

    :rtype: None
    """
    try:
        assert obj in _SUFFIX_TUPLE
    except AssertionError:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract()
def increasing_real_numpy_vector(obj):
    r"""
    Validate if an object is :ref:`IncreasingRealNumpyVector` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    if _check_increasing_real_numpy_vector(obj):
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract()
def number_numpy_vector(obj):
    r"""
    Validate if an object is a :ref:`NumberNumpyVector` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    if _check_number_numpy_vector(obj):
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract()
def real_numpy_vector(obj):
    r"""
    Validate if an object is a :ref:`RealNumpyVector` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    if _check_real_numpy_vector(obj):
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract()
def touchstone_data(obj):
    r"""
    Validate if an object is an :ref:`TouchstoneData` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    if (not isinstance(obj, dict)) or (
        isinstance(obj, dict)
        and (sorted(obj.keys()) != sorted(["points", "freq", "pars"]))
    ):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if not (isinstance(obj["points"], int) and (obj["points"] > 0)):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if _check_increasing_real_numpy_vector(obj["freq"]):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if not isinstance(obj["pars"], np.ndarray):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    vdata = ["int", "float", "complex"]
    if not any([obj["pars"].dtype.name.startswith(item) for item in vdata]):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if obj["freq"].size != obj["points"]:
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    nports = int(math.sqrt(obj["pars"].size / obj["freq"].size))
    if obj["points"] * (nports ** 2) != obj["pars"].size:
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract()
def touchstone_noise_data(obj):
    r"""
    Validate if an object is an :ref:`TouchstoneNoiseData` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    if isinstance(obj, dict) and (not obj):
        return None
    if (not isinstance(obj, dict)) or (
        isinstance(obj, dict)
        and (sorted(obj.keys()) != sorted(["points", "freq", "nf", "rc", "res"]))
    ):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if not (isinstance(obj["points"], int) and (obj["points"] > 0)):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if _check_increasing_real_numpy_vector(obj["freq"]):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if _check_real_numpy_vector(obj["nf"]):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if _check_number_numpy_vector(obj["rc"]):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if not (
        isinstance(obj["res"], np.ndarray)
        and (len(obj["res"].shape) == 1)
        and (obj["res"].shape[0] > 0)
        and np.all(obj["res"] >= 0)
    ):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    sizes = [obj["freq"].size, obj["nf"].size, obj["rc"].size, obj["res"].size]
    if set(sizes) != set([obj["points"]]):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    return None

@pexdoc.pcontracts.new_contract()
def touchstone_options(obj):
    r"""
    Validate if an object is an :ref:`TouchstoneOptions` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    if (not isinstance(obj, dict)) or (
        isinstance(obj, dict)
        and (sorted(obj.keys()) != sorted(["units", "ptype", "pformat", "z0"]))
    ):
        raise ValueError(pexdoc.pcontracts.get_exdesc())
    if not (
        (obj["units"].lower() in ["ghz", "mhz", "khz", "hz"])
        and (obj["ptype"].lower() in ["s", "y", "z", "h", "g"])
        and (obj["pformat"].lower() in ["db", "ma", "ri"])
        and isinstance(obj["z0"], float)
        and (obj["z0"] >= 0)
    ):
        raise ValueError(pexdoc.pcontracts.get_exdesc())

@pexdoc.pcontracts.new_contract()
def wave_interp_option(obj):
    r"""
    Validate if an object is a :ref:`WaveInterpOption` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    exdesc = pexdoc.pcontracts.get_exdesc()
    if not isinstance(obj, str):
        raise ValueError(exdesc)
    if obj.upper() in ["CONTINUOUS", "STAIRCASE"]:
        return None
    raise ValueError(exdesc)

@pexdoc.pcontracts.new_contract()
def wave_scale_option(obj):
    r"""
    Validate if an object is a :ref:`WaveScaleOption` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    exdesc = pexdoc.pcontracts.get_exdesc()
    if not isinstance(obj, str):
        raise ValueError(exdesc)
    if obj.upper() in ["LINEAR", "LOG"]:
        return None
    raise ValueError(exdesc)

@pexdoc.pcontracts.new_contract()
def wave_vectors(obj):
    r"""
    Validate if an object is a :ref:`WaveVectors` pseudo-type object.

    :param obj: Object
    :type  obj: any

    :raises: RuntimeError (Argument \`*[argument_name]*\` is not valid). The
     token \*[argument_name]\* is replaced by the name of the argument the
     contract is attached to

    :rtype: None
    """
    exdesc = pexdoc.pcontracts.get_exdesc()
    if not isinstance(obj, list) or (isinstance(obj, list) and not obj):
        raise ValueError(exdesc)
    if any([not (isinstance(item, tuple) and len(item) == 2) for item in obj]):
        raise ValueError(exdesc)
    indep_vector, dep_vector = zip(*obj)
    if _check_increasing_real_numpy_vector(np.array(indep_vector)):
        raise ValueError(exdesc)
    if _check_real_numpy_vector(np.array(dep_vector)):
        raise ValueError(exdesc)