source: sasview/sansmodels/src/c_models/disperser.c @ 442b800

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 442b800 was 101065a, checked in by Mathieu Doucet <doucetm@…>, 13 years ago

keep only header files in c_extensions and rename it 'include'

  • Property mode set to 100644
File size: 13.4 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  step = 0.0;
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
449//static 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.