def notice(self, mins, maxes, axislist, ignore=False): ignore = bool_cast(ignore) if str in [type(min) for min in mins]: raise DataErr('typecheck', 'lower bound') elif str in [type(max) for max in maxes]: raise DataErr('typecheck', 'upper bound') elif str in [type(axis) for axis in axislist]: raise DataErr('typecheck', 'grid') mask = filter_bins(mins, maxes, axislist) if mask is None: self.mask = not ignore elif not ignore: if self.mask is True: self.mask = mask else: self.mask |= mask else: mask = ~mask if self.mask is False: self.mask = mask else: self.mask &= mask
def test_filter_bins_empty(los, his, axis): """Ensure filter_bins returns None if one input is empty.""" # just check the input parameters include an empty array assert len(los) == 0 or len(his) == 0 or len(axis) == 0 assert utils.filter_bins(los, his, [axis]) is None
def prepare(self, data, src, lo=None, hi=None): # Note: src is source model before folding if not isinstance(data, DataPHA): raise IOErr('notpha', data.name) lo, hi = bounds_check(lo, hi) self.units = data.units if self.units == "channel": warning("Channel space is unappropriate for the PHA unfolded" + " source model,\nusing energy.") self.units = "energy" self.xlabel = data.get_xlabel() self.title = f'Source Model of {data.name}' self.xlo, self.xhi = data._get_indep(filter=False) # Why do we not apply the mask at the end of prepare? # self.mask = filter_bins((lo, ), (hi, ), (self.xlo, )) # The source model is assumed to not contain an instrument model, # and so it evaluates the expected number of photons/cm^2/s in # each bin (or it can be thought of as a 1 second exposure). # self.y = src(self.xlo, self.xhi) prefix_quant = 'E' quant = 'keV' if self.units == "wavelength": # No other labels use the LaTeX forms for lambda and # Angstrom, so use the text version here too. # prefix_quant = to_latex('\\lambda') # quant = to_latex('\\AA') prefix_quant = 'lambda' quant = 'Angstrom' (self.xlo, self.xhi) = (self.xhi, self.xlo) xmid = abs(self.xhi - self.xlo) sqr = to_latex('^2') self.xlabel = f'{self.units.capitalize()} ({quant})' self.ylabel = f'%s Photons/sec/cm{sqr}%s' if data.plot_fac == 0: self.y /= xmid self.ylabel = self.ylabel % (f'f({prefix_quant})', f'/{quant} ') elif data.plot_fac == 1: self.ylabel = self.ylabel % (f'{prefix_quant} f({prefix_quant})', '') elif data.plot_fac == 2: self.y *= xmid self.ylabel = self.ylabel % ( f'{prefix_quant}{sqr} f({prefix_quant})', f' {quant} ') else: raise PlotErr('plotfac', 'Source', data.plot_fac)
def test_filter_bins_two_none(): """Use two different arrays for filtering with no filter. """ y1 = [1, 2, 3, 4, 5] y2 = [10, 20, 30, 40, 50] flags = utils.filter_bins((None, None), (None, None), (y1, y2)) assert flags is None
def test_filter_bins_unordered(): """What happens if the array is unordered?""" flags = utils.filter_bins((3, ), (8, ), [[1,4,3,7,8,10,5]]) expected = [False, True, True, True, True, False, True] assert len(flags) == len(expected) for got, exp in zip(flags, expected): assert got == exp
def test_filter_bins_one(lo, hi, res): """Can we filter the array between [lo,hi] [unintegrated]? This test is a regression test rather than a from-first-principles test: this is mainly relevant for the edge cases like: lo=hi and lo>hi. """ dvals = numpy.asarray([1, 2, 3, 4, 5]) flags = utils.filter_bins([lo], [hi], [dvals]) assert flags == pytest.approx(res) # We can also check an identity: that # a <= x <= b # is the same as # a <= x # x <= b # flags = utils.filter_bins([lo, None], [None, hi], [dvals, dvals]) assert flags == pytest.approx(res)
def prepare(self, data, src, lo=None, hi=None): # Note: src is source model before folding if not isinstance(data, DataPHA): raise IOErr('notpha', data.name) lo, hi = bounds_check(lo, hi) self.units = data.units if self.units == "channel": warning("Channel space is unappropriate for the PHA unfolded" + " source model,\nusing energy.") self.units = "energy" self.xlabel = data.get_xlabel() self.title = 'Source Model of %s' % data.name self.xlo, self.xhi = data._get_indep(filter=False) self.mask = filter_bins((lo, ), (hi, ), (self.xlo, )) self.y = src(self.xlo, self.xhi) prefix_quant = 'E' quant = 'keV' if self.units == "wavelength": # No other labels use the LaTeX forms for lambda and # Angstrom, so use the text version here too. # prefix_quant = to_latex('\\lambda') # quant = to_latex('\\AA') prefix_quant = 'lambda' quant = 'Angstrom' (self.xlo, self.xhi) = (self.xhi, self.xlo) xmid = abs(self.xhi - self.xlo) sqr = to_latex('^2') self.xlabel = '%s (%s)' % (self.units.capitalize(), quant) self.ylabel = '%s Photons/sec/cm' + sqr + '%s' if data.plot_fac == 0: self.y /= xmid self.ylabel = self.ylabel % ('f(%s)' % prefix_quant, '/%s ' % quant) elif data.plot_fac == 1: self.ylabel = self.ylabel % ('%s f(%s)' % (prefix_quant, prefix_quant), '') elif data.plot_fac == 2: self.y *= xmid self.ylabel = self.ylabel % ('%s%s f(%s)' % (prefix_quant, sqr, prefix_quant), ' %s ' % quant) else: raise PlotErr('plotfac', 'Source', data.plot_fac)
def prepare(self, data, src, lo=None, hi=None): # Note: src is source model before folding if not isinstance(data, DataPHA): raise IOErr('notpha', data.name) lo, hi = bounds_check(lo, hi) self.units = data.units if self.units == "channel": warning("Channel space is unappropriate for the PHA unfolded" + " source model,\nusing energy.") self.units = "energy" self.xlabel = data.get_xlabel() self.title = 'Source Model of %s' % data.name self.xlo, self.xhi = data._get_indep(filter=False) self.mask = filter_bins((lo,), (hi,), (self.xlo,)) self.y = src(self.xlo, self.xhi) prefix_quant = 'E' quant = 'keV' if self.units == "wavelength": # No other labels use the LaTeX forms for lambda and # Angstrom, so use the text version here too. # prefix_quant = to_latex('\\lambda') # quant = to_latex('\\AA') prefix_quant = 'lambda' quant = 'Angstrom' (self.xlo, self.xhi) = (self.xhi, self.xlo) xmid = abs(self.xhi - self.xlo) sqr = to_latex('^2') self.xlabel = '%s (%s)' % (self.units.capitalize(), quant) self.ylabel = '%s Photons/sec/cm' + sqr + '%s' if data.plot_fac == 0: self.y /= xmid self.ylabel = self.ylabel % ('f(%s)' % prefix_quant, '/%s ' % quant) elif data.plot_fac == 1: self.ylabel = self.ylabel % ('%s f(%s)' % (prefix_quant, prefix_quant), '') elif data.plot_fac == 2: self.y *= xmid self.ylabel = self.ylabel % ('%s%s f(%s)' % (prefix_quant, sqr, prefix_quant), ' %s ' % quant) else: raise PlotErr('plotfac', 'Source', data.plot_fac)
def test_filter_bins_two(lo1, lo2, hi1, hi2, expected): """Use two different arrays for filtering. This version uses tuples rather than arrays as the input arguments to filter_bins. """ y1 = [1, 2, 3, 4, 5] y2 = [10, 20, 30, 40, 50] flags = utils.filter_bins((lo1, lo2), (hi1, hi2), (y1, y2)) assert len(flags) == 5 for i in range(5): assert flags[i] == expected[i], i
def test_filter_bins_one_int(lo, hi, res): """Can we filter the array between [lo,hi) [integrated]? This test is a regression test rather than a from-first-principles test: this is mainly relevant for the edge cases like: lo=hi and lo>hi. The test replicates the logic of Data1DInt.notice """ lovals = numpy.asarray([1, 2, 3, 4, 5]) hivals = lovals + 1 flags = utils.filter_bins([None, lo], [hi, None], [lovals, hivals], integrated=True) assert flags == pytest.approx(res)
def _flux(data, lo, hi, src, eflux=False, srcflux=False): lo, hi = bounds_check(lo, hi) axislist = None if hasattr(data, '_get_indep'): axislist = data._get_indep(filter=False) else: axislist = data.get_indep(filter=False) y = src(*axislist) if srcflux and len(axislist) > 1: y /= numpy.asarray(axislist[1] - axislist[0]) dim = numpy.asarray(axislist).squeeze().ndim if eflux: # for energy flux, the sum of grid below must be in keV. energ = [] for axis in axislist: grid = axis if hasattr(data, 'units') and data.units == 'wavelength': grid = data._hc / grid energ.append(grid) if dim == 1: y = numpy.asarray(0.5 * y * energ[0], SherpaFloat) elif dim == 2: y = numpy.asarray(0.5 * y * (energ[0] + energ[1]), SherpaFloat) else: raise IOErr('>axes', "2") mask = filter_bins((lo,), (hi,), (axislist[0],)) val = y.sum() if mask is not None: flux = y[mask] # flux density at a single bin -> divide by bin width. if dim == 2 and len(flux) == 1: flux /= numpy.abs(axislist[1][mask] - axislist[0][mask]) val = flux.sum() if eflux: val *= _charge_e return val
def prepare(self, data, src, lo=None, hi=None): # Note: src is source model before folding if not isinstance(data, DataPHA): raise IOErr("notpha", data.name) lo, hi = bounds_check(lo, hi) self.units = data.units if self.units == "channel": warning("Channel space is unappropriate for the PHA unfolded" + " source model,\nusing energy.") self.units = "energy" self.xlabel = data.get_xlabel() self.title = "Source Model of %s" % data.name self.xlo, self.xhi = data._get_indep(filter=False) self.mask = filter_bins((lo,), (hi,), (self.xlo,)) self.y = src(self.xlo, self.xhi) prefix_quant = "E" quant = "keV" if self.units == "wavelength": prefix_quant = "\\lambda" quant = "\\AA" (self.xlo, self.xhi) = (self.xhi, self.xlo) xmid = abs(self.xhi - self.xlo) self.xlabel = "%s (%s)" % (self.units.capitalize(), quant) self.ylabel = "%s Photons/sec/cm^2%s" if data.plot_fac == 0: self.y /= xmid self.ylabel = self.ylabel % ("f(%s)" % prefix_quant, "/%s " % quant) elif data.plot_fac == 1: self.ylabel = self.ylabel % ("%s f(%s)" % (prefix_quant, prefix_quant), "") elif data.plot_fac == 2: self.y *= xmid self.ylabel = self.ylabel % ("%s^{2} f(%s)" % (prefix_quant, prefix_quant), " %s " % quant) else: raise PlotErr("plotfac", "Source", data.plot_fac)
def eqwidth(data, model, combo, lo=None, hi=None): lo, hi = bounds_check(lo, hi) my = None cy = None xlo = None xhi = None num = None eqw = 0.0 if hasattr(data, 'get_response'): xlo, xhi = data._get_indep(filter=False) my = model(xlo, xhi) cy = combo(xlo, xhi) num = len(xlo) else: my = data.eval_model_to_fit(model) cy = data.eval_model_to_fit(combo) xlo = data.get_indep(filter=True)[0] num = len(xlo) # TODO: should this follow _flux and handle the case when # we have xlo, xhi differently? # mask = filter_bins((lo, ), (hi, ), (xlo, )) if mask is not None: my = my[mask] cy = cy[mask] xlo = xlo[mask] num = len(xlo) for ebin, val in enumerate(xlo): if ebin < (num - 1): eave = numpy.abs(xlo[ebin + 1] - xlo[ebin]) else: eave = numpy.abs(xlo[ebin - 1] - xlo[ebin]) if my[ebin] != 0.0: eqw += eave * (cy[ebin] - my[ebin]) / my[ebin] return eqw
def eqwidth(data, model, combo, lo=None, hi=None): lo, hi = bounds_check(lo, hi) my = None cy = None xlo = None xhi = None num = None eqw = 0.0 if hasattr(data, 'get_response'): xlo, xhi = data._get_indep(filter=False) my = model(xlo, xhi) cy = combo(xlo, xhi) num = len(xlo) else: my = data.eval_model_to_fit(model) cy = data.eval_model_to_fit(combo) xlo = data.get_indep(filter=True)[0] num = len(xlo) mask = filter_bins((lo,), (hi,), (xlo,)) if mask is not None: my = my[mask] cy = cy[mask] xlo = xlo[mask] num = len(xlo) for ebin, val in enumerate(xlo): if ebin < (num - 1): eave = numpy.abs(xlo[ebin + 1] - xlo[ebin]) else: eave = numpy.abs(xlo[ebin - 1] - xlo[ebin]) if my[ebin] != 0.0: eqw += eave * (cy[ebin] - my[ebin]) / my[ebin] return eqw
def test_filter_bins_one(lo, hi, res): """Can we filter the array between lo and hi?""" dvals = numpy.asarray([1, 2, 3, 4, 5]) flags = utils.filter_bins([lo], [hi], [dvals]) assert (flags == res).all()
def _flux(data, lo, hi, src, eflux=False, srcflux=False): lo, hi = bounds_check(lo, hi) try: method = data._get_indep except AttributeError: method = data.get_indep axislist = method(filter=False) dim = numpy.asarray(axislist).squeeze().ndim if dim > 2: raise IOErr('>axes', "2") # assume this should not happen, so we do not have to worry # about a nice error message assert dim > 0 # To make things simpler, evaluate on the full grid y = src(*axislist) if srcflux and dim == 2: y /= numpy.asarray(axislist[1] - axislist[0]) if eflux: # for energy flux, the sum of grid below must be in keV. # energ = [] convert = hasattr(data, 'units') and data.units == 'wavelength' for axis in axislist: grid = axis if convert: grid = data._hc / grid energ.append(grid) if dim == 2: ecorr = 0.5 * (energ[0] + energ[1]) else: # why multiply by 0.5? ecorr = 0.5 * energ[0] y *= ecorr # What bins do we use for the calculation? Linear interpolation # is used for bin edges (for integrated data sets) # if dim == 1: mask = filter_bins((lo, ), (hi, ), (axislist[0], )) assert mask is not None # no bin found if numpy.all(~mask): return 0.0 # convert boolean to numbers scale = 1.0 * mask else: scale = range_overlap_1dint(axislist, lo, hi) if scale is None: return 0.0 assert scale.max() > 0 # Originally a flux density was calculated if both lo and hi # fell in the same bin, but this has been changed so that # we only calculate a density if the lo and hi values are the # same (which is set by bounds_check when a density is requested). # if lo is not None and dim == 2 and lo == hi: assert scale.sum() == 1, 'programmer error: sum={}'.format(scale.sum()) y /= numpy.abs(axislist[1] - axislist[0]) flux = (scale * y).sum() if eflux: flux *= _charge_e return flux
def test_filter_bins_scalar_array(axval, flag): """Edge case: do we care about this result?""" f = utils.filter_bins([1], [5], [axval]) assert f == flag
def test_filter_bins_scalar_array_empty(): """Edge case: do we care about this result?""" f = utils.filter_bins([1], [2], [[]]) assert f.dtype == bool assert len(f) == 0