/** * Disperser * * Python class that takes a model and averages its output * for a distribution of its parameters. * * The parameters to be varied are specified at instantiation time. * The distributions are Gaussian, with std deviations specified for * each parameter at instantiation time. * * @author M.Doucet / UTK */ #include #include "structmember.h" #include #include #include #include /// Error object for raised exceptions static PyObject * DisperserError = NULL; // Class definition typedef struct { PyObject_HEAD /// Dictionary of parameters for this class PyObject * params; /// Model object to disperse PyObject * model; /// Array of original values double * centers; /// Number of parameters to disperse int n_pars; /// Coordinate system flag int cylindrical; } Disperser; // Function definitions double disperseParam(Disperser *self, int iPar, PyObject * pars); double Disperser_readDouble(PyObject *p); /** * Function to deallocate memory for the object */ static void Disperser_dealloc(Disperser * self) { free(self->centers); self->ob_type->tp_free((PyObject*)self); } /** * Function to create a new Disperser object */ static PyObject * Disperser_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Disperser *self; self = (Disperser *)type->tp_alloc(type, 0); return (PyObject *)self; } /** * Initialization of the object (__init__) */ static int Disperser_init(Disperser *self, PyObject *args, PyObject *kwds) { PyObject * model; PyObject * paramList; PyObject * sigmaList; if (self != NULL) { // Create parameters self->params = PyDict_New(); // Get input if ( !PyArg_ParseTuple(args,"OOO",&model, ¶mList, &sigmaList) ) { PyErr_SetString(DisperserError, "Disperser: could not parse to initialize. Need Disperser(model, paramList, sigmaList)"); return -1; } // Check that the lists are of the right type if( PyList_Check(paramList)!=1 || PyList_Check(sigmaList)!=1 ) { PyErr_SetString(DisperserError, "Disperser: bad input parameters; did not find lists."); return -1; } // Check that the list lengths are compatible if( PyList_GET_SIZE(paramList) != PyList_GET_SIZE(sigmaList) ) { PyErr_SetString(DisperserError, "Disperser: supplied lists are not of the same length."); return -1; } // Set the data members self->model = model; // Number of parameters to disperse // Let run() find out since values can change self->n_pars = 0; // Set default coordinate system as cartesian self->cylindrical = 0; // Initialize parameter dictionary // Store the list of parameter names (strings) PyDict_SetItemString(self->params,"paramList", paramList); // Store the list of std deviations (floats) PyDict_SetItemString(self->params,"sigmaList", sigmaList); // Set the default number of points to average over PyDict_SetItemString(self->params,"n_pts",Py_BuildValue("i",25)); } return 0; } /** * List of member to make public * Only the parameters and the model are available to python users */ static PyMemberDef Disperser_members[] = { {"params", T_OBJECT, offsetof(Disperser, params), 0, "Parameters"}, {"model", T_OBJECT, offsetof(Disperser, model), 0, "Model to disperse"}, {NULL} /* Sentinel */ }; /** Read double from PyObject * TODO: THIS SHOULD REALLY BE ELSEWHERE! * @param p PyObject * @return double */ double Disperser_readDouble(PyObject *p) { double value = 0.0; if (PyFloat_Check(p)==1) { value = (double)(((PyFloatObject *)(p))->ob_fval); //Py_DECREF(p); return value; } else if (PyInt_Check(p)==1) { value = (double)(((PyIntObject *)(p))->ob_ival); //Py_DECREF(p); return value; } else if (PyLong_Check(p)==1) { value = (double)PyLong_AsLong(p); //Py_DECREF(p); return value; } else { //Py_DECREF(p); return 0.0; } } /** * Function to call to evaluate model * @param args: input q or [qx,qy] * @return: function value */ static PyObject * runXY(Disperser *self, PyObject *args) { PyObject* pars; int n_pts; int i; double value; PyObject * result; PyObject * temp; // Get parameters // Check that the lists are of the right type if( PyList_Check(PyDict_GetItemString(self->params, "paramList"))!=1 || PyList_Check(PyDict_GetItemString(self->params, "sigmaList"))!=1 ) { PyErr_SetString(DisperserError, "Disperser: bad input parameters; did not find lists."); return NULL; } // Reader parameter dictionary n_pts = PyInt_AsLong( PyDict_GetItemString(self->params, "n_pts") ); // Check that the list lengths are compatible if( PyList_GET_SIZE(PyDict_GetItemString(self->params, "paramList")) != PyList_GET_SIZE(PyDict_GetItemString(self->params, "sigmaList")) ) { PyErr_SetString(DisperserError, "Disperser: supplied lists are not of the same length."); return NULL; } // Number of parameters to disperse self->n_pars = PyList_GET_SIZE(PyDict_GetItemString(self->params, "paramList")); // Allocate memory for centers free(self->centers); self->centers = (double *)malloc(self->n_pars * sizeof(double)); if(self->centers==NULL) { PyErr_SetString(DisperserError, "Disperser.run could not allocate memory."); return NULL; } // Store initial values of those parameters for( i=0; in_pars; i++ ) { result = PyObject_CallMethod(self->model, "getParam", "(O)", PyList_GetItem( PyDict_GetItemString(self->params, "paramList"), i )); if( result == NULL ) { PyErr_SetString(DisperserError, "Disperser.run could not get current parameter values."); return NULL; } else { self->centers[i] = Disperser_readDouble(result); Py_DECREF(result); }; } // Get input and determine whether we have to supply a 1D or 2D return value. if ( !PyArg_ParseTuple(args,"O",&pars) ) { PyErr_SetString(DisperserError, "Disperser.run expects a q value."); return NULL; } // Evaluate or calculate average if( self->n_pars > 0 ) { value = disperseParam(self, 0, pars); } else { if (self->cylindrical==1) { result = PyObject_CallMethod(self->model, "run", "(O)",pars); } else { result = PyObject_CallMethod(self->model, "runXY", "(O)",pars); } if( result == NULL ) { PyErr_SetString(DisperserError, "Disperser.run failed."); return NULL; } else { Py_DECREF(result); return result; } } // Put back the original model parameter values for( i=0; in_pars; i++ ) { // Maybe we need to decref the double here? temp = Py_BuildValue("d", self->centers[i]); result = PyObject_CallMethod(self->model, "setParam", "(OO)", PyList_GetItem( PyDict_GetItemString(self->params, "paramList"), i ), temp ); Py_DECREF(temp); if( result == NULL ) { PyErr_SetString(DisperserError, "Disperser.run could not set back original parameter values."); return NULL; } else { Py_DECREF(result); }; } return Py_BuildValue("d",value); } /** * Function to call to evaluate model * @param args: input q or [q,phi] * @return: function value */ static PyObject * run(Disperser *self, PyObject *args) { PyObject * output; self->cylindrical = 1; output = runXY(self, args); self->cylindrical = 0; return output; } /** * Gaussian weight * @param mean: mean value of the Gaussian * @param sigma: standard deviation of the Gaussian * @param x: value at which the Gaussian is evaluated * @return: value of the Gaussian */ double gaussian_weight(double mean, double sigma, double x) { double vary, expo_value; vary = x-mean; expo_value = -vary*vary/(2*sigma*sigma); //return 1.0; return exp(expo_value); } /** * @param iPar: ID of parameter to disperse * @param pars: input parameter to the evaluation function */ double disperseParam(Disperser *self, int iPar, PyObject * pars) { double sigma; double min_value, max_value; double step; double prev_value; double value_sum; double gauss_sum; double gauss_value; double func_value; double error_sys; double value; int i, n_pts; PyObject *result; PyObject *temp; step = 0.0; n_pts = PyInt_AsLong( PyDict_GetItemString(self->params, "n_pts") ); // If we exhausted the parameter array, simply evaluate // the model if( iPar < self->n_pars ) { // Average over Gaussian distribution (2 sigmas) value_sum = 0.0; gauss_sum = 0.0; // Get the standard deviation for this parameter sigma = Disperser_readDouble(PyList_GetItem( PyDict_GetItemString(self->params, "sigmaList"), iPar )); // Average over 4 sigmas wide min_value = self->centers[iPar] - 2*sigma; max_value = self->centers[iPar] + 2*sigma; // Calculate step size step = (max_value - min_value)/(n_pts-1); // If we are not changing the parameter, just return the // value of the function if (step == 0.0) { return disperseParam(self, iPar+1, pars); } // Compute average prev_value = 0.0; error_sys = 0.0; for( i=0; imodel, "setParam", "(OO)", PyList_GetItem( PyDict_GetItemString(self->params, "paramList"), iPar ), temp); Py_DECREF(temp); if( result == NULL ) { printf("Could not set param %i\n", iPar); // return a value that will create an NAN return 0.0/(step-step); } else { Py_DECREF(result); } gauss_value = gaussian_weight(self->centers[iPar], sigma, value); func_value = disperseParam(self, iPar+1, pars); value_sum += gauss_value * func_value; gauss_sum += gauss_value; } return value_sum/gauss_sum; } else { if (self->cylindrical==1) { result = PyObject_CallMethod(self->model, "run", "(O)",pars); } else { result = PyObject_CallMethod(self->model, "runXY", "(O)",pars); } if( result == NULL ) { printf("Model.run() could not return\n"); // return a value that will create an NAN return 0.0/(step-step); } else { value = Disperser_readDouble(result); Py_DECREF(result); return value; } } } static PyMethodDef Disperser_methods[] = { {"run", (PyCFunction)run , METH_VARARGS, "Evaluate the model at a given input value"}, {"runXY", (PyCFunction)runXY , METH_VARARGS, "Evaluate the model at a given input value"}, {NULL} }; static PyTypeObject DisperserType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Disperser", /*tp_name*/ sizeof(Disperser), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Disperser_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ "Disperser objects", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Disperser_methods, /* tp_methods */ Disperser_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Disperser_init, /* tp_init */ 0, /* tp_alloc */ Disperser_new, /* tp_new */ }; //static PyMethodDef module_methods[] = { // {NULL} //}; /** * Function used to add the model class to a module * @param module: module to add the class to */ void addDisperser(PyObject *module) { PyObject *d; if (PyType_Ready(&DisperserType) < 0) return; Py_INCREF(&DisperserType); PyModule_AddObject(module, "Disperser", (PyObject *)&DisperserType); d = PyModule_GetDict(module); DisperserError = PyErr_NewException("Disperser.error", NULL, NULL); PyDict_SetItemString(d, "DisperserError", DisperserError); }