"""
Module with generic functions used when modeling data.

----

.. include:: ../include/copy.rst

----

.. include common links, assuming primary doc root is up one directory
"""

import numpy
from .filter import BoxcarFilter

[docs]def reject_residuals_1d(resid, lo=3.0, hi=3.0, boxcar=None):
r"""
Reject fit residuals.

Args:
resid (numpy.ndarray_, numpy.ma.MaskedArray_):
Weighted fit residuals. If input as a numpy.ndarray_,
all data is included in the calculation of the local or
global standard deviation. To exclude values from this
calculation, the input *must* be a masked array. Shape
must be either :math:(N_x,) or :math:(N_{\rm
vec},N_x}). If 2D, the rejection is performed separately
for each *row*.
lo (:obj:float, optional):
Sigma rejection for low values.
hi (:obj:float, optional):
Sigma rejection for high values.
boxcar (:obj:int, optional):
Size of a boxcar to use if using local sigma
rejection. If None, rejection is based on the global
standard deviation.

Returns:
numpy.ndarray_: Boolean array flagging values to reject.
"""
if boxcar is None:
mean = numpy.ma.mean(resid, axis=-1)
std = numpy.ma.std(resid, axis=-1)
# NOTE: Transposes avoid the need for numpy.newaxis usage.
return ((numpy.ma.asarray(resid).data.T < mean - lo * std) \
| (numpy.ma.asarray(resid).data.T > mean + hi * std)).T \

bf = BoxcarFilter(boxcar, lo=lo, hi=hi, niter=1, y=resid, local_sigma=True)

# TODO: Should maybe change the behavior of this so that if rng=None,
# the range is set to x[[0,-1]].
[docs]def scaled_coordinates(x, rng=None):
r"""
Scale the provided coordinates.

The coordinates are scaled such that over the provided range the
coordinates are rescaled to the range :math:[-1, 1].

.. warning::

If rng is None, the returned object is identical to x
(i.e., it's not a copy of it).

Args:
x (numpy.ndarray_):
Coordinate array
rng (array-like, optional):
The range over which to rescale to [-1,1]. If None, the
input coordinates are simply returned.

Returns:
numpy.ndarray_: The rescaled coordinates.

"""
return x if rng is None else 2 * (x - rng[0]) / (rng[1] - rng[0]) - 1

[docs]class FitResiduals:
def __init__(self, y, model, gpm=None):
self.y = y
self.model = model
self.gpm = gpm
def __call__(self, a):
resid = (self.y-self.model(a))
return resid if self.gpm is None else resid[self.gpm]

[docs]class FitChiSquare:
def __init__(self, y, e, model, gpm=None):
self.y = y
self.e = e
self.model = model
_gpm = self.e > 0
self.gpm = _gpm if gpm is None else gpm & _gpm
def __call__(self, a):
return (self.y[self.gpm]-self.model(a)[self.gpm])/self.e[self.gpm]

`