Skip to content

Commit

Permalink
Implemented numpy.frompyfunc to reduce the number of failures in scip…
Browse files Browse the repository at this point in the history
…y.stats

regression tests.
  • Loading branch information
Jason McCampbell (Enthought, Inc) committed Apr 26, 2011
1 parent efc722b commit 3b9a522
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 4 deletions.
34 changes: 34 additions & 0 deletions numpy/NumpyDotNet/NpyAccessLib/Npy_UFunc_Access.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,40 @@ extern "C" __declspec(dllexport)



//
// What does this mess do?! Basically it is a gateway between a user-supplied Python function
// implementing a ufunc and the native core.
//
// managedPyFunc is the managed function that loops over the array, similar to the PyUFunc_Om_On
// function in the CPython interface. This function calls the user-supplied function once per
// loop iteration.
//
// PyUFunc_Om_On here is simply a translation layer that breaks up the NpyUFunc_FuncData struct
// into separate arguments so we don't need to reproduce the struct memory layout in the managed
// world.
//
// NpyUFuncAccess_UFuncFromPyFunc actually constructs the natived UFunc instance and passes it back
// to the managed world.
typedef void (*IPyUFuncGenericFunction)(char **args, npy_intp *, npy_intp *, int, int, void *);

IPyUFuncGenericFunction managedPyFunc = NULL;

static void PyUFunc_Om_On(char **args, npy_intp *dims, npy_intp *steps, NpyUFunc_FuncData *funcData)
{
managedPyFunc(args, dims, steps, funcData->nin, funcData->nout, funcData->callable);
}
static NpyUFuncGenericFunction pyfunc_functions[] = { (NpyUFuncGenericFunction)PyUFunc_Om_On };

extern "C" __declspec(dllexport)
void *NpyUFuncAccess_UFuncFromPyFunc(int nin, int nout, char *funcName, void *pyLoopFunc, void *func)
{
managedPyFunc = (IPyUFuncGenericFunction)pyLoopFunc;
return npy_ufunc_frompyfunc(nin, nout, funcName, strlen(funcName), pyfunc_functions, func);
}





static void InitOtherOperators(void *dictionary,
void (*addToDict)(void *dictObj, const char *funcStr, void *ufuncobj)) {
Expand Down
25 changes: 24 additions & 1 deletion numpy/NumpyDotNet/NpyCoreApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,25 @@ internal static void Dealloc(IntPtr obj) {
}


/// <summary>
/// Constructs a native ufunc object from a Python function. The inputs define the
/// number of arguments taken, number of outputs, and function name. The pyLoopFunc
/// function implements the iteration over a given array and should always by PyUFunc_Om_On.
/// pyFunc is the actual function object to call.
/// </summary>
/// <param name="nin">Number of input arguments</param>
/// <param name="nout">Number of result values (a PythonTuple if > 1)</param>
/// <param name="funcName">Name of the function</param>
/// <param name="pyWrapperFunc">PyUFunc_Om_On, implements looping over the array</param>
/// <param name="pyFunc">Function to call</param>
internal static IntPtr UFuncFromPyFunc(int nin, int nout, String funcName,
IntPtr pyWrapperFunc, IntPtr pyFunc) {
lock (GlobalIterpLock) {
return NpyUFuncAccess_UFuncFromPyFunc(nin, nout, funcName, pyWrapperFunc, pyFunc);
}
}


[DllImport("NpyAccessLib", CallingConvention = CallingConvention.Cdecl)]
internal static extern void NpyUFuncAccess_Init(IntPtr funcDict,
IntPtr funcDefs, IntPtr callMethodFunc, IntPtr addToDictFunc);
Expand Down Expand Up @@ -1779,6 +1798,9 @@ private static extern int NpyArrayAccess_SetDateTimeInfo(IntPtr descr,
[DllImport("NpyAccessLib", CallingConvention = CallingConvention.Cdecl, EntryPoint="NpyArrayAccess_DictFreeIter")]
internal static extern void NpyDict_FreeIter(IntPtr iter);

[DllImport("NpyAccessLib", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr NpyUFuncAccess_UFuncFromPyFunc(int nin, int nout, String funcName, IntPtr pyThunk, IntPtr func);

/// <summary>
/// Accesses the next dictionary item, returning the key and value. Thread-safe when operating across
/// separate iterators; caller must ensure that one iterator is not access simultaneously from two
Expand Down Expand Up @@ -1939,7 +1961,7 @@ internal unsafe struct NpyArray_ArrayDescr {
/// <summary>
/// Offset to the interface pointer.
/// </summary>
private static int Offset_InterfacePtr = (int)Marshal.OffsetOf(typeof(NpyObject_HEAD), "nob_interface");
internal static int Offset_InterfacePtr = (int)Marshal.OffsetOf(typeof(NpyObject_HEAD), "nob_interface");

/// <summary>
/// Offset to the reference count in the header structure.
Expand Down Expand Up @@ -1971,6 +1993,7 @@ internal static TResult ToInterface<TResult>(IntPtr ptr) {
return (TResult)GCHandleFromIntPtr(wrapper).Target;
}


/// <summary>
/// Same as ToInterface but releases the core reference.
/// </summary>
Expand Down
92 changes: 92 additions & 0 deletions numpy/NumpyDotNet/umath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,98 @@ public static void seterrobj(List obj) {



/// <summary>
/// Implements a loop over n input arrays and calls a user-provided Python function for each
/// element. func must be a pointer to a GCHandle referencing the Python function.
/// </summary>
/// <param name="args">Array of pointers to object arrays. args[n] corresponds to each
/// input array followed by each output array pointer</param>
/// <param name="dimensions">Dimension[0] is the number of elements to loop over</param>
/// <param name="steps">Offset to next element, each steps[n] is offset for args[n] array</param>
/// <param name="nin">Number of input arguments to function</param>
/// <param name="nout">Number of return values from function</param>
/// <param name="func">GCHandle referencing Python function to call</param>
private static unsafe void PyUFunc_Om_On(IntPtr* args, IntPtr* dimensions, IntPtr* steps, int nin, int nout, IntPtr func) {
long n = (long)dimensions[0];
object f = NpyCoreApi.GCHandleFromIntPtr(func).Target;
int ntot = nin + nout;
IntPtr[] ptrs = new IntPtr[ntot];

// Initialize the pointers, we will advance these by 'step' each iteration.
for (int j = 0; j < ntot; j++) {
ptrs[j] = args[j];
}

object[] arglist = new object[nin];
for (long i = 0; i < n; i++) {
// First nin values are inputs to the function.
for (int j = 0; j < nin; j++) {
IntPtr* v = (IntPtr*)ptrs[j];
arglist[j] = (*v != IntPtr.Zero) ?
NpyCoreApi.GCHandleFromIntPtr(*v).Target : null;
}
object result = PythonCalls.Call(f, arglist);
if (result == null) return; // Failed.

// Result of the function is either a tuple of nout values or a single value.
PythonTuple tup = result as PythonTuple;
if (tup != null) {
if (nout != tup.Count) {
throw new ArgumentException(String.Format("Invalid return tuple size {0}, expected {1}",
tup.Count, nout));
}
for (int j = 0; j < nout; j++) {
// If we have a GCHandle already in this slot, overwrite the target. Otherwise
// we alloc a new GCHandle
IntPtr* elem = (IntPtr*)(ptrs[j + nin]);
if (*elem != IntPtr.Zero) {
GCHandle h = NpyCoreApi.GCHandleFromIntPtr(*elem);
h.Target = tup[j];
} else {
*elem = GCHandle.ToIntPtr(NpyCoreApi.AllocGCHandle(tup[j]));
}
}
} else {
IntPtr *elem = (IntPtr *)ptrs[nin];
if (*elem != IntPtr.Zero) {
GCHandle h = NpyCoreApi.GCHandleFromIntPtr(*elem);
h.Target = result;
} else {
*elem = GCHandle.ToIntPtr(NpyCoreApi.AllocGCHandle(result));
}
}

for (int j = 0; j < ntot; j++) {
ptrs[j] = IntPtr.Add(ptrs[j], steps[j].ToInt32());
}
}
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private unsafe delegate void del_PyUFunc_Om_On(IntPtr* args, IntPtr* dimensions, IntPtr* steps, int nin, int nout, IntPtr func);
private static unsafe readonly del_PyUFunc_Om_On PyUFunc_Om_On_Delegate = new del_PyUFunc_Om_On(PyUFunc_Om_On);

public static object frompyfunc(CodeContext cntx, object func, int nin, int nout) {
if (!IronPython.Runtime.Operations.PythonOps.IsCallable(cntx, func)) {
throw new ArgumentTypeException("function must be callback");
}

string funcName = (string)PythonOps.ObjectGetAttribute(cntx, func, "__name__") ?? "?";
GCHandle funcHandle = NpyCoreApi.AllocGCHandle(func);
IntPtr ufunc = NpyCoreApi.UFuncFromPyFunc(nin, nout, funcName,
Marshal.GetFunctionPointerForDelegate(PyUFunc_Om_On_Delegate), GCHandle.ToIntPtr(funcHandle));

// Allocate a managed wrapper for the ufunc, then set the native object's interface pointer
// to this object. Once that is done, we can decref the native object and it won't go away
// because it's owned by the managed object.
ufunc self = new ufunc(ufunc);
Marshal.WriteIntPtr(ufunc, NpyCoreApi.Offset_InterfacePtr,
GCHandle.ToIntPtr(NpyCoreApi.AllocGCHandle(self)));
NpyCoreApi.Decref(ufunc);
return self;
}


/// <summary>
/// Map of function names to all defined ufunc objects.
/// </summary>
Expand Down
3 changes: 0 additions & 3 deletions numpy/core/umath_clr.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,3 @@
e = math.e
from NumpyDotNet.umath import *

def frompyfunc(*args):
raise NotImplementedError()

0 comments on commit 3b9a522

Please sign in to comment.