Numpy-Vectorized Python Local Models

NumPy-vectorized Python local models for XSPEC.

Decorate a local model function with vectorized() to have XSPEC hand it zero-copy numpy arrays -- views over XSPEC's own storage -- instead of Python tuples and lists. A vectorized model can be an order of magnitude faster than the classic interface because it avoids boxing every array element into a Python float and lets the model body use numpy array operations.

The calling convention is identical to a classic addPyMod / lmod model except for the argument types:

  • engs (nE+1 bin edges) and params arrive as read-only numpy arrays;

  • flux (and the optional fluxErr) arrive as writable numpy arrays that must be filled IN PLACE -- assign with flux[:] = ..., never rebind flux = .... Rebinding discards the result, exactly as it does for the classic list interface.

Example:

import xspec
import numpy as np

@xspec.vectorized
def myModel(engs, params, flux):
    de = engs[1:] - engs[:-1]
    flux[:] = params[0] * de

parInfo = ('norm  ""  1.0  0.0  0.0  1e6  1e6  0.01',)
xspec.AllModels.addPyMod(myModel, parInfo, 'add')

The model function may take 3, 4, or 5 positional arguments (engs, params, flux[, fluxErr[, spectrumNumber]]) -- the same convention as a classic Python model. A model that takes fluxErr fills it in place; if it leaves it untouched, XSPEC reports no error array. spectrumNumber is passed as a plain int.

xspec.vectorized(func)

Mark a Python local model function as numpy-vectorized.

Returns a wrapper -- with the same positional arity as func -- that XSPEC recognizes (via the _xspec_vectorized attribute) as wanting numpy-array arguments over its internal buffers. See the module docstring for the calling convention.

The wrapper is a thin adapter: it reinterprets each incoming memoryview as a numpy array with numpy.frombuffer() (zero copy; dtype defaults to float64) and forwards to func. Filling flux/fluxErr in place therefore writes straight back into XSPEC's storage.