OpenMPCD
Fit.py
1 """
2 @package MPCDAnalysis.Fit
3 
4 Provides commonly used data fitting functionality.
5 """
6 
7 class Fit:
8  """
9  Helps fitting data to arbitrary functions, and handling the results.
10  """
11 
12  def __init__(self):
13  """
14  The constructor.
15  """
16 
17  self._fitResults = None
18 
19 
20  def fit(
21  self, f, xData, yData, yErrs = None,
22  lowerBounds = None, upperBounds = None):
23  """
24  Fits the given data to the given fit function `f`.
25 
26  After calling this, any previous fits with this instance, and the
27  corresponding fit results, are discarded.
28 
29  @throw TypeError
30  Throws if any argument is of the wrong type.
31  @throw ValueError
32  Throws if `xData`, `yData`, and `yErrs` (if not `None`) have
33  different lengths.
34  @throw ValueError
35  Throws if `lowerBounds` or `upperBounds` has the wrong length,
36  or invalid values.
37 
38  @param[in] f
39  The function to fit to; it must be supplied as a callable
40  object, taking, as its first parameter, an instance of
41  `numpy.ndarray` containing all the independent variable
42  values to evaluate the fit function for. This callable must
43  then return an instance of `numpy.ndarray` containing, in the
44  same order, the fit function values corresponding to the
45  elements in the first argument.
46  Any further arguments this callable takes are assumed to be
47  fit parameters that will be optimized for.
48  @param[in] xData
49  An iterable containing the independent variable values to fit
50  with.
51  @param[in] yData
52  An iterable containing the target values, which the fit
53  should approach as much as possible. The order must
54  correspond to `xData`.
55  @param[in] yErrs
56  An iterable containing, in an order corresponding to `yData`,
57  the uncertainties (one standard deviation error) on the
58  elements on `yData`.
59  @param[in] lowerBounds
60  Either `None` to have no lower bounds on the fitting
61  parameters, or a `list`, the length of which corresponds to
62  the number of fitting parameters, where each value represents
63  the lower bound for the corresponding fitting parameter.
64  Values of `None` in this list correspond to no bound for that
65  parameter.
66  @param[in] upperBounds
67  As in `lowerBounds`, except that upper bounds are specified.
68  """
69 
70  if not callable(f):
71  raise TypeError()
72 
73  if len(xData) != len(yData):
74  raise ValueError()
75  if yErrs is not None:
76  if len(yErrs) != len(yData):
77  raise ValueError()
78 
79  for x in [lowerBounds, upperBounds]:
80  from MPCDAnalysis.Utilities import getNumberOfArgumentsFromCallable
81  if x is not None:
82  if not isinstance(x, list):
83  raise TypeError()
84  if len(x) != getNumberOfArgumentsFromCallable(f) - 1:
85  raise TypeError()
86 
87  import numpy
88  import scipy.optimize
89 
90  if isinstance(xData, numpy.ndarray):
91  xValues = xData
92  else:
93  try:
94  xValues = numpy.array(xData)
95  except:
96  xValues = numpy.array([])
97  for val in xData:
98  xValues = numpy.append(xValues, [val])
99 
100  if isinstance(yData, numpy.ndarray):
101  yValues = yData
102  else:
103  try:
104  yValues = numpy.array(yData)
105  except:
106  yValues = numpy.array([])
107  for val in yData:
108  yValues = numpy.append(yValues, [val])
109 
110  if yErrs is None:
111  yErrors = yErrs
112  absoluteErrors = False
113  else:
114  absoluteErrors = True
115 
116  if isinstance(yErrs, numpy.ndarray):
117  yErrors = yErrs
118  else:
119  try:
120  yErrors = numpy.array(yErrors)
121  except:
122  yErrors = numpy.array([])
123  for val in yErrs:
124  yErrors = numpy.append(yErrors, [val])
125 
126 
127  bounds = (-numpy.inf, numpy.inf)
128 
129  if lowerBounds is not None:
130  _proper = []
131  for b in lowerBounds:
132  if b is None:
133  b = -numpy.inf
134  _proper.append(b)
135  bounds = (_proper, bounds[1])
136 
137  if upperBounds is not None:
138  _proper = []
139  for b in upperBounds:
140  if b is None:
141  b = numpy.inf
142  _proper.append(b)
143  bounds = (bounds[0], _proper)
144 
145 
146  import warnings
147  with warnings.catch_warnings():
148  warnings.simplefilter("ignore", scipy.optimize.OptimizeWarning)
149 
150  self._fitResults = \
151  scipy.optimize.curve_fit(
152  f,
153  xValues,
154  yValues,
155  sigma = yErrors,
156  absolute_sigma = absoluteErrors,
157  bounds = bounds)
158 
159 
160  def getFitParameterCount(self):
161  """
162  Returns the number of fit parameters used in the last successful call
163  to `fit`.
164 
165  @throw RuntimeError
166  Throws if `fit` has not been called successfully previously.
167  """
168 
169  if self._fitResults is None:
170  raise RuntimeError()
171 
172  return len(self._fitResults[0])
173 
174 
175  def getFitParameter(self, index):
176  """
177  Returns the parameter estimate for the parameter with the given `index`.
178 
179  @throw RuntimeError
180  Throws if `fit` has not been called successfully previously.
181  @throw TypeError
182  Throws if any argument is of the wrong type.
183  @throw ValueError
184  Throws if `index` is out of bounds.
185 
186  @param[in] index
187  The index of the fit parameter, which must be a non-negative
188  `int` that is smaller than `getFitParameterCount()`.
189  """
190 
191 
192  if self._fitResults is None:
193  raise RuntimeError()
194 
195  if not isinstance(index, int):
196  raise TypeError()
197 
198  if index < 0 or index >= self.getFitParameterCount():
199  raise ValueError()
200 
201  return self._fitResults[0][index]
202 
203 
204  def getFitParameterVariance(self, index):
205  """
206  Returns the variance of the parameter estimate for the parameter with
207  the given `index`, if such a variance is available, or `numpy.inf` if no
208  variance estimate was possible.
209 
210  @throw RuntimeError
211  Throws if `fit` has not been called successfully previously.
212  @throw TypeError
213  Throws if any argument is of the wrong type.
214  @throw ValueError
215  Throws if `index` is out of bounds.
216 
217  @param[in] index
218  The index of the fit parameter, which must be a non-negative
219  `int` that is smaller than `getFitParameterCount()`.
220  """
221 
222 
223  if self._fitResults is None:
224  raise RuntimeError()
225 
226  if not isinstance(index, int):
227  raise TypeError()
228 
229  if index < 0 or index >= self.getFitParameterCount():
230  raise ValueError()
231 
232  return self._fitResults[1][index][index]
233 
234 
235  def getFitParameterStandardDeviation(self, index):
236  """
237  Returns the standard deviation of the parameter estimate for the
238  parameter with the given `index`, if such a standard deviation is
239  available, or `numpy.inf` if no standard deviation estimate was
240  possible.
241 
242  @throw RuntimeError
243  Throws if `fit` has not been called successfully previously.
244  @throw TypeError
245  Throws if any argument is of the wrong type.
246  @throw ValueError
247  Throws if `index` is out of bounds.
248 
249  @param[in] index
250  The index of the fit parameter, which must be a non-negative
251  `int` that is smaller than `getFitParameterCount()`.
252  """
253 
254  import numpy
255  return numpy.sqrt(self.getFitParameterVariance(index))
256 
257 
258  def getFitParameterCovariance(self, index1, index2):
259  """
260  Returns the covariance of the parameter estimates for the parameter with
261  the given indices, if such a covariance is available, or `numpy.inf` if
262  no coviariance estimate was possible.
263 
264  @throw RuntimeError
265  Throws if `fit` has not been called successfully previously.
266  @throw TypeError
267  Throws if any argument is of the wrong type.
268  @throw ValueError
269  Throws if either `index1` or `index2` is out of bounds.
270 
271  @param[in] index1
272  The index of one of the fit parameters, which must be a
273  non-negative `int` that is smaller than
274  `getFitParameterCount()`.
275  @param[in] index2
276  The index of one of the fit parameters, which must be a
277  non-negative `int` that is smaller than
278  `getFitParameterCount()`.
279  """
280 
281  if self._fitResults is None:
282  raise RuntimeError()
283 
284  if not isinstance(index1, int):
285  raise TypeError()
286  if not isinstance(index2, int):
287  raise TypeError()
288 
289  if index1 < 0 or index1 >= self.getFitParameterCount():
290  raise ValueError()
291  if index2 < 0 or index2 >= self.getFitParameterCount():
292  raise ValueError()
293 
294  return self._fitResults[1][index1][index2]
295 
296 
297  @staticmethod
298  def multipleIndependentDataFits(f, dataSet):
299  """
300  Independently fits multiple sets of data to the given fit function `f`.
301 
302  @throw TypeError
303  Throws if any argument is of the wrong type.
304  @throw ValueError
305  Throws if any argument has an invalid value.
306 
307  @param[in] f
308  The function to fit to; it must satisfy the conditions for
309  the `f` argument to `fit`.
310  @param[in] dataSet
311  An iterable, with elements being lists of length `3`; the
312  list elements will be passed as the `xData`, `yData`, and
313  `yErrs` parameters to `fit`, and accordingly need to satisfy
314  the conditions there.
315 
316  @return Returns a list of dictionaries, in the order that corresponds
317  to the order in `dataSet`; each dictionary contains the
318  following elements:
319  - `data`, the list returned while iterating `dataSet`
320  - `fit`, an instance of this class, on which `fit` has been
321  called with the given `f` and the `data` elements, as
322  described in the documentation for the `dataSet` parameter.
323  """
324 
325  ret = []
326 
327  for data in dataSet:
328  fit = Fit()
329  fit.fit(f, data[0], data[1], data[2])
330  ret.append({'data': data, 'fit': fit})
331 
332  return ret
333 
334 
335  @staticmethod
336  def getBestOutOfMultipleIndependentDataFits(f, dataSet, comparator):
337  """
338  Independently fits multiple sets of data to the given fit function `f`,
339  and returns the best fit, as determined by `comparator`.
340 
341  This function returns the equivalent to the result of the following
342  procedure:
343  First, let `results = multipleIndependentDataFits(f, dataSet)`.
344  Then, if `results` is empty, return `None`. Otherweise, start by calling
345  the first result in the list the `best` result, and iterate through all
346  other results, with iteration variable `currentResult`. For each
347  iteration, call `comparator(best, currentResult)`; if it returns `True`,
348  let `best = currentResult`.
349  After having completed all iterations, return `best`.
350 
351  @throw TypeError
352  Throws if any argument is of the wrong type.
353  @throw ValueError
354  Throws if any argument has an invalid value.
355 
356  @param[in] f
357  The function to fit to; it must satisfy the conditions for
358  the `f` argument to `fit`.
359  @param[in] dataSet
360  An iterable, with elements being lists of length `3`; the
361  list elements will be passed as the `xData`, `yData`, and
362  `yErrs` parameters to `fit`, and accordingly need to satisfy
363  the conditions there.
364  @param[in] comparator
365  The callable used to compare two fits; see the main body
366  of this function documentation for details.
367  """
368 
369  results = Fit.multipleIndependentDataFits(f, dataSet)
370 
371  if not results:
372  return None
373 
374  best = None
375  for result in results:
376  if best is None:
377  best = result
378  continue
379 
380  if comparator(best, result):
381  best = result
382 
383  return best
384 
385 
386  def __eq__(self, rhs):
387  """
388  Returns whether this instance and `rhs` have the same state.
389 
390  @throw TypeError
391  Throws if any argument is of the wrong type.
392 
393  @param[in] rhs
394  The right-hand-side instance of this class.
395  """
396 
397  if not isinstance(rhs, self.__class__):
398  raise TypeError()
399 
400  if len(self.__dict__) != len(rhs.__dict__):
401  return False
402 
403  import numpy
404  for k, v in self.__dict__.items():
405  if k not in rhs.__dict__:
406  return False
407 
408  if k == "_fitResults":
409  if v is None:
410  if rhs.__dict__[k] is not None:
411  return False
412  else:
413  if len(v) != len(rhs.__dict__[k]):
414  return False
415 
416  for x, y in enumerate(v):
417  if numpy.any(y != rhs.__dict__[k][x]):
418  return False
419  else:
420  print(type(v))
421  if v != rhs.__dict__[k]:
422  return False
423 
424  return True
425 
426 
427  def __ne__(self, rhs):
428  """
429  Returns the negation of `self == rhs`.
430 
431  @throw TypeError
432  Throws if any argument is of the wrong type.
433 
434  @param[in] rhs
435  The right-hand-side instance of this class.
436  """
437 
438  return not self == rhs
MPCDAnalysis.Fit.Fit.__eq__
def __eq__(self, rhs)
Definition: Fit.py:407
MPCDAnalysis.Fit.Fit.getFitParameterCovariance
def getFitParameterCovariance(self, index1, index2)
Definition: Fit.py:288
MPCDAnalysis.Fit.Fit.fit
def fit(self, f, xData, yData, yErrs=None, lowerBounds=None, upperBounds=None)
Definition: Fit.py:70
MPCDAnalysis.Fit.Fit._fitResults
_fitResults
Definition: Fit.py:20
MPCDAnalysis.Fit.Fit
Definition: Fit.py:12
MPCDAnalysis.Fit.Fit.multipleIndependentDataFits
def multipleIndependentDataFits(f, dataSet)
Definition: Fit.py:333
MPCDAnalysis.Fit.Fit.getFitParameter
def getFitParameter(self, index)
Definition: Fit.py:195
MPCDAnalysis.Fit.Fit.__ne__
def __ne__(self, rhs)
Definition: Fit.py:449
MPCDAnalysis.Utilities
Definition: Utilities.py:1
MPCDAnalysis.Fit.Fit.__init__
def __init__(self)
Definition: Fit.py:18
MPCDAnalysis.Fit.Fit.getFitParameterCount
def getFitParameterCount(self)
Definition: Fit.py:172
MPCDAnalysis.Fit.Fit.getBestOutOfMultipleIndependentDataFits
def getBestOutOfMultipleIndependentDataFits(f, dataSet, comparator)
Definition: Fit.py:378
MPCDAnalysis.Fit.Fit.getFitParameterVariance
def getFitParameterVariance(self, index)
Definition: Fit.py:227
MPCDAnalysis.Fit.Fit.getFitParameterStandardDeviation
def getFitParameterStandardDeviation(self, index)
Definition: Fit.py:260