source: sasview/sansmodels/src/sans/models/c_extensions/disperser.c @ 59b9b675

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 59b9b675 was 59b9b675, checked in by Jae Cho <jhjcho@…>, 13 years ago

1) reverting diperser(used only in test) files for unittest. 2)changed the sigma of polydispersion to ratio for length parameters. 3) update an unittest

  • Property mode set to 100644
File size: 13.8 KB
Line 
1/**
2 * Disperser
3 *
4 * Python class that takes a model and averages its output
5 * for a distribution of its parameters.
6 *
7 * The parameters to be varied are specified at instantiation time.
8 * The distributions are Gaussian, with std deviations specified for
9 * each parameter at instantiation time.
10 *
11 * @author   M.Doucet / UTK
12 */
13 
14#include <Python.h>
15#include "structmember.h"
16#include <stdio.h>
17#include <stdlib.h>
18#include <math.h>
19#include <time.h>
20
21/// Error object for raised exceptions
22static PyObject * DisperserError = NULL;
23
24// Class definition
25typedef struct {
26    PyObject_HEAD
27    /// Dictionary of parameters for this class
28    PyObject * params;
29    /// Model object to disperse
30    PyObject * model;
31    /// Array of original values
32    double * centers;
33    /// Number of parameters to disperse
34    int n_pars;
35    /// Coordinate system flag
36    int cylindrical;
37} Disperser;
38
39// Function definitions
40double disperseParam(Disperser *self, int iPar, PyObject * pars);
41double Disperser_readDouble(PyObject *p);
42
43/**
44 * Function to deallocate memory for the object
45 */
46static void
47Disperser_dealloc(Disperser * self) {
48    free(self->centers);
49    self->ob_type->tp_free((PyObject*)self);
50}
51
52/**
53 * Function to create a new Disperser object
54 */
55static PyObject *
56Disperser_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
57{
58    Disperser *self;
59   
60    self = (Disperser *)type->tp_alloc(type, 0);
61   
62    return (PyObject *)self;
63}
64
65/**
66 * Initialization of the object (__init__)
67 */
68static int
69Disperser_init(Disperser *self, PyObject *args, PyObject *kwds)
70{
71        PyObject * model;
72        PyObject * paramList;
73        PyObject * sigmaList;
74       
75    if (self != NULL) {
76       
77        // Create parameters
78        self->params = PyDict_New();
79       
80                // Get input
81                if ( !PyArg_ParseTuple(args,"OOO",&model, &paramList, &sigmaList) ) {
82                    PyErr_SetString(DisperserError, 
83                        "Disperser: could not parse to initialize. Need Disperser(model, paramList, sigmaList)");
84                        return -1;
85                }
86
87                // Check that the lists are of the right type
88                if( PyList_Check(paramList)!=1 || PyList_Check(sigmaList)!=1 ) {
89                        PyErr_SetString(DisperserError,
90                                "Disperser: bad input parameters; did not find lists.");
91                        return -1;
92                }
93               
94                // Check that the list lengths are compatible
95                if( PyList_GET_SIZE(paramList) != PyList_GET_SIZE(sigmaList) ) {
96                        PyErr_SetString(DisperserError,
97                                "Disperser: supplied lists are not of the same length.");
98                        return -1;
99                } 
100               
101                // Set the data members
102                self->model = model;
103               
104                // Number of parameters to disperse
105                // Let run() find out since values can change
106                self->n_pars = 0;
107
108                // Set default coordinate system as cartesian
109                self->cylindrical = 0;
110
111        // Initialize parameter dictionary
112        // Store the list of parameter names (strings)
113        PyDict_SetItemString(self->params,"paramList", paramList);
114       
115        // Store the list of std deviations (floats)
116        PyDict_SetItemString(self->params,"sigmaList", sigmaList);
117       
118        // Set the default number of points to average over
119        PyDict_SetItemString(self->params,"n_pts",Py_BuildValue("i",25));
120    }
121    return 0;
122}
123
124/**
125 * List of member to make public
126 * Only the parameters and the model are available to python users
127 */
128static PyMemberDef Disperser_members[] = {
129    {"params", T_OBJECT, offsetof(Disperser, params), 0,
130     "Parameters"},
131    {"model", T_OBJECT, offsetof(Disperser, model), 0,
132     "Model to disperse"},
133    {NULL}  /* Sentinel */
134};
135
136/** Read double from PyObject
137 *      TODO: THIS SHOULD REALLY BE ELSEWHERE!
138 *   @param p PyObject
139 *   @return double
140 */
141double Disperser_readDouble(PyObject *p) {
142        double value = 0.0;
143       
144    if (PyFloat_Check(p)==1) {
145        value = (double)(((PyFloatObject *)(p))->ob_fval);
146        //Py_DECREF(p);
147        return value;
148    } else if (PyInt_Check(p)==1) {
149        value = (double)(((PyIntObject *)(p))->ob_ival);
150        //Py_DECREF(p);
151        return value;
152    } else if (PyLong_Check(p)==1) {
153        value = (double)PyLong_AsLong(p);
154        //Py_DECREF(p);
155        return value;
156    } else {
157        //Py_DECREF(p);         
158        return 0.0;
159    }
160}
161
162
163/**
164 * Function to call to evaluate model
165 * @param args: input q or [qx,qy]
166 * @return: function value
167 */
168static PyObject * runXY(Disperser *self, PyObject *args) {
169        PyObject* pars;
170        int n_pts;
171        int i;
172        double value;
173        PyObject * result;
174        PyObject * temp;
175       
176        // Get parameters
177       
178        // Check that the lists are of the right type
179        if( PyList_Check(PyDict_GetItemString(self->params, "paramList"))!=1 
180                || PyList_Check(PyDict_GetItemString(self->params, "sigmaList"))!=1 ) {
181                PyErr_SetString(DisperserError,
182                        "Disperser: bad input parameters; did not find lists.");
183                return NULL;
184        }
185
186        // Reader parameter dictionary
187    n_pts = PyInt_AsLong( PyDict_GetItemString(self->params, "n_pts") );
188   
189        // Check that the list lengths are compatible
190        if( PyList_GET_SIZE(PyDict_GetItemString(self->params, "paramList")) 
191                != PyList_GET_SIZE(PyDict_GetItemString(self->params, "sigmaList")) ) {
192                PyErr_SetString(DisperserError,
193                        "Disperser: supplied lists are not of the same length.");
194                return NULL;
195        } 
196
197    // Number of parameters to disperse
198    self->n_pars = PyList_GET_SIZE(PyDict_GetItemString(self->params, "paramList"));
199   
200    // Allocate memory for centers
201    free(self->centers);
202    self->centers = (double *)malloc(self->n_pars * sizeof(double));
203        if(self->centers==NULL) {
204            PyErr_SetString(DisperserError, 
205                "Disperser.run could not allocate memory.");
206                return NULL;
207        }
208   
209    // Store initial values of those parameters   
210    for( i=0; i<self->n_pars; i++ ) {
211        result = PyObject_CallMethod(self->model, "getParam", "(O)", 
212                PyList_GetItem( PyDict_GetItemString(self->params, "paramList"), i ));
213               
214        if( result == NULL ) {
215                    PyErr_SetString(DisperserError, 
216                        "Disperser.run could not get current parameter values.");
217                return NULL;
218        } else {
219                self->centers[i] = Disperser_readDouble(result);
220                Py_DECREF(result);
221        }; 
222    }
223       
224        // Get input and determine whether we have to supply a 1D or 2D return value.
225        if ( !PyArg_ParseTuple(args,"O",&pars) ) {
226            PyErr_SetString(DisperserError, 
227                "Disperser.run expects a q value.");
228                return NULL;
229        }   
230       
231    // Evaluate or calculate average
232    if( self->n_pars > 0 ) {
233                value = disperseParam(self, 0, pars);
234    } else {
235         if (self->cylindrical==1) {
236                result = PyObject_CallMethod(self->model, "run", "(O)",pars);
237         } else {
238                result = PyObject_CallMethod(self->model, "runXY", "(O)",pars);
239         }
240         
241         if( result == NULL ) {
242                    PyErr_SetString(DisperserError, 
243                        "Disperser.run failed.");
244                return NULL;           
245         } else {
246                Py_DECREF(result);
247                return result;
248         }
249    }
250   
251    // Put back the original model parameter values
252    for( i=0; i<self->n_pars; i++ ) {
253        // Maybe we need to decref the double here?
254        temp = Py_BuildValue("d", self->centers[i]);
255        result = PyObject_CallMethod(self->model, "setParam", "(OO)", 
256                PyList_GetItem( PyDict_GetItemString(self->params, "paramList"), i ),
257                temp );
258        Py_DECREF(temp);
259               
260        if( result == NULL ) {
261                    PyErr_SetString(DisperserError, 
262                        "Disperser.run could not set back original parameter values.");
263                return NULL;
264        } else {
265                Py_DECREF(result);
266        }; 
267    }
268   
269        return Py_BuildValue("d",value);       
270
271}
272
273/**
274 * Function to call to evaluate model
275 * @param args: input q or [q,phi]
276 * @return: function value
277 */
278static PyObject * run(Disperser *self, PyObject *args) {
279        PyObject * output;
280        self->cylindrical = 1;
281        output = runXY(self, args);
282        self->cylindrical = 0;
283        return output;
284}
285
286
287
288
289/**
290 * Gaussian weight
291 * @param mean: mean value of the Gaussian
292 * @param sigma: standard deviation of the Gaussian
293 * @param x: value at which the Gaussian is evaluated
294 * @return: value of the Gaussian
295 */
296double gaussian_weight(double mean, double sigma, double x) {
297        double vary, expo_value;
298    vary = x-mean;
299    expo_value = -vary*vary/(2*sigma*sigma);
300    //return 1.0;
301    return exp(expo_value);
302}
303
304/**
305 * @param iPar: ID of parameter to disperse
306 * @param pars: input parameter to the evaluation function
307 */
308double disperseParam(Disperser *self, int iPar, PyObject * pars) {
309        double sigma;
310        double min_value, max_value;
311        double step;
312        double prev_value;
313        double value_sum;
314        double gauss_sum;
315        double gauss_value;
316        double func_value;
317        double error_sys;
318        double value;
319        int i, n_pts;
320        PyObject *result;
321        PyObject *temp;
322       
323       
324    n_pts = PyInt_AsLong( PyDict_GetItemString(self->params, "n_pts") );
325   
326        // If we exhausted the parameter array, simply evaluate
327    // the model
328    if( iPar < self->n_pars ) {
329           
330                // Average over Gaussian distribution (2 sigmas)
331        value_sum = 0.0;
332        gauss_sum = 0.0;
333           
334        // Get the standard deviation for this parameter
335        sigma = Disperser_readDouble(PyList_GetItem( PyDict_GetItemString(self->params, "sigmaList"), iPar ));
336           
337        // Average over 4 sigmas wide
338        min_value = self->centers[iPar] - 2*sigma;
339        max_value = self->centers[iPar] + 2*sigma;
340           
341        // Calculate step size
342        step = (max_value - min_value)/(n_pts-1);
343           
344        // If we are not changing the parameter, just return the
345        // value of the function
346        if (step == 0.0) {
347            return disperseParam(self, iPar+1, pars);
348        }
349           
350        // Compute average
351        prev_value = 0.0;
352        error_sys  = 0.0;
353        for( i=0; i<n_pts; i++ ) {
354            // Set the parameter value           
355            value = min_value + (double)i*step;
356               
357            temp = Py_BuildValue("d", value);
358                result = PyObject_CallMethod(self->model, "setParam", "(OO)",
359                        PyList_GetItem( PyDict_GetItemString(self->params, "paramList"), iPar ), 
360                                temp);
361                Py_DECREF(temp);
362                       
363                    if( result == NULL ) {
364                                printf("Could not set param %i\n", iPar);
365                        // return a value that will create an NAN
366                        return 0.0/(step-step);
367                    } else {
368                        Py_DECREF(result);
369                    }
370               
371                        gauss_value = gaussian_weight(self->centers[iPar], sigma, value);
372            func_value = disperseParam(self, iPar+1, pars);
373
374            value_sum += gauss_value * func_value;
375                gauss_sum += gauss_value;       
376        }   
377        return value_sum/gauss_sum;
378       
379    } else {
380         if (self->cylindrical==1) {
381                result = PyObject_CallMethod(self->model, "run", "(O)",pars);
382         } else {
383                result = PyObject_CallMethod(self->model, "runXY", "(O)",pars);
384         }
385         if( result == NULL ) {
386                printf("Model.run() could not return\n");
387                // return a value that will create an NAN
388                return 0.0/(step-step);
389         } else {
390                value = Disperser_readDouble(result);                   
391                Py_DECREF(result);
392                return value;
393         }
394    }
395       
396}
397
398static PyMethodDef Disperser_methods[] = {
399    {"run",      (PyCFunction)run     , METH_VARARGS,
400      "Evaluate the model at a given input value"},
401    {"runXY",      (PyCFunction)runXY     , METH_VARARGS,
402      "Evaluate the model at a given input value"},
403   {NULL}
404};
405
406static PyTypeObject DisperserType = {
407    PyObject_HEAD_INIT(NULL)
408    0,                         /*ob_size*/
409    "Disperser",             /*tp_name*/
410    sizeof(Disperser),             /*tp_basicsize*/
411    0,                         /*tp_itemsize*/
412    (destructor)Disperser_dealloc, /*tp_dealloc*/
413    0,                         /*tp_print*/
414    0,                         /*tp_getattr*/
415    0,                         /*tp_setattr*/
416    0,                         /*tp_compare*/
417    0,                         /*tp_repr*/
418    0,                         /*tp_as_number*/
419    0,                         /*tp_as_sequence*/
420    0,                         /*tp_as_mapping*/
421    0,                         /*tp_hash */
422    0,                         /*tp_call*/
423    0,                         /*tp_str*/
424    0,                         /*tp_getattro*/
425    0,                         /*tp_setattro*/
426    0,                         /*tp_as_buffer*/
427    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
428    "Disperser objects",           /* tp_doc */
429    0,                         /* tp_traverse */
430    0,                         /* tp_clear */
431    0,                         /* tp_richcompare */
432    0,                         /* tp_weaklistoffset */
433    0,                         /* tp_iter */
434    0,                         /* tp_iternext */
435    Disperser_methods,             /* tp_methods */
436    Disperser_members,             /* tp_members */
437    0,                         /* tp_getset */
438    0,                         /* tp_base */
439    0,                         /* tp_dict */
440    0,                         /* tp_descr_get */
441    0,                         /* tp_descr_set */
442    0,                         /* tp_dictoffset */
443    (initproc)Disperser_init,      /* tp_init */
444    0,                         /* tp_alloc */
445    Disperser_new,                 /* tp_new */
446};
447
448
449static PyMethodDef module_methods[] = {
450    {NULL} 
451};
452
453/**
454 * Function used to add the model class to a module
455 * @param module: module to add the class to
456 */ 
457void addDisperser(PyObject *module) {
458        PyObject *d;
459       
460    if (PyType_Ready(&DisperserType) < 0)
461        return;
462
463    Py_INCREF(&DisperserType);
464    PyModule_AddObject(module, "Disperser", (PyObject *)&DisperserType);
465   
466    d = PyModule_GetDict(module);
467    DisperserError = PyErr_NewException("Disperser.error", NULL, NULL);
468    PyDict_SetItemString(d, "DisperserError", DisperserError);
469}
470
Note: See TracBrowser for help on using the repository browser.