12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142 |
- """
- Python wrappers for Orthogonal Distance Regression (ODRPACK).
- Notes
- =====
- * Array formats -- FORTRAN stores its arrays in memory column first, i.e., an
- array element A(i, j, k) will be next to A(i+1, j, k). In C and, consequently,
- NumPy, arrays are stored row first: A[i, j, k] is next to A[i, j, k+1]. For
- efficiency and convenience, the input and output arrays of the fitting
- function (and its Jacobians) are passed to FORTRAN without transposition.
- Therefore, where the ODRPACK documentation says that the X array is of shape
- (N, M), it will be passed to the Python function as an array of shape (M, N).
- If M==1, the 1-D case, then nothing matters; if M>1, then your
- Python functions will be dealing with arrays that are indexed in reverse of
- the ODRPACK documentation. No real issue, but watch out for your indexing of
- the Jacobians: the i,jth elements (@f_i/@x_j) evaluated at the nth
- observation will be returned as jacd[j, i, n]. Except for the Jacobians, it
- really is easier to deal with x[0] and x[1] than x[:,0] and x[:,1]. Of course,
- you can always use the transpose() function from SciPy explicitly.
- * Examples -- See the accompanying file test/test.py for examples of how to set
- up fits of your own. Some are taken from the User's Guide; some are from
- other sources.
- * Models -- Some common models are instantiated in the accompanying module
- models.py . Contributions are welcome.
- Credits
- =======
- * Thanks to Arnold Moene and Gerard Vermeulen for fixing some killer bugs.
- Robert Kern
- robert.kern@gmail.com
- """
- import os
- import numpy
- from warnings import warn
- from scipy.odr import __odrpack
- __all__ = ['odr', 'OdrWarning', 'OdrError', 'OdrStop',
- 'Data', 'RealData', 'Model', 'Output', 'ODR',
- 'odr_error', 'odr_stop']
- odr = __odrpack.odr
- class OdrWarning(UserWarning):
- """
- Warning indicating that the data passed into
- ODR will cause problems when passed into 'odr'
- that the user should be aware of.
- """
- pass
- class OdrError(Exception):
- """
- Exception indicating an error in fitting.
- This is raised by `~scipy.odr.odr` if an error occurs during fitting.
- """
- pass
- class OdrStop(Exception):
- """
- Exception stopping fitting.
- You can raise this exception in your objective function to tell
- `~scipy.odr.odr` to stop fitting.
- """
- pass
- # Backwards compatibility
- odr_error = OdrError
- odr_stop = OdrStop
- __odrpack._set_exceptions(OdrError, OdrStop)
- def _conv(obj, dtype=None):
- """ Convert an object to the preferred form for input to the odr routine.
- """
- if obj is None:
- return obj
- else:
- if dtype is None:
- obj = numpy.asarray(obj)
- else:
- obj = numpy.asarray(obj, dtype)
- if obj.shape == ():
- # Scalar.
- return obj.dtype.type(obj)
- else:
- return obj
- def _report_error(info):
- """ Interprets the return code of the odr routine.
- Parameters
- ----------
- info : int
- The return code of the odr routine.
- Returns
- -------
- problems : list(str)
- A list of messages about why the odr() routine stopped.
- """
- stopreason = ('Blank',
- 'Sum of squares convergence',
- 'Parameter convergence',
- 'Both sum of squares and parameter convergence',
- 'Iteration limit reached')[info % 5]
- if info >= 5:
- # questionable results or fatal error
- I = (info//10000 % 10,
- info//1000 % 10,
- info//100 % 10,
- info//10 % 10,
- info % 10)
- problems = []
- if I[0] == 0:
- if I[1] != 0:
- problems.append('Derivatives possibly not correct')
- if I[2] != 0:
- problems.append('Error occurred in callback')
- if I[3] != 0:
- problems.append('Problem is not full rank at solution')
- problems.append(stopreason)
- elif I[0] == 1:
- if I[1] != 0:
- problems.append('N < 1')
- if I[2] != 0:
- problems.append('M < 1')
- if I[3] != 0:
- problems.append('NP < 1 or NP > N')
- if I[4] != 0:
- problems.append('NQ < 1')
- elif I[0] == 2:
- if I[1] != 0:
- problems.append('LDY and/or LDX incorrect')
- if I[2] != 0:
- problems.append('LDWE, LD2WE, LDWD, and/or LD2WD incorrect')
- if I[3] != 0:
- problems.append('LDIFX, LDSTPD, and/or LDSCLD incorrect')
- if I[4] != 0:
- problems.append('LWORK and/or LIWORK too small')
- elif I[0] == 3:
- if I[1] != 0:
- problems.append('STPB and/or STPD incorrect')
- if I[2] != 0:
- problems.append('SCLB and/or SCLD incorrect')
- if I[3] != 0:
- problems.append('WE incorrect')
- if I[4] != 0:
- problems.append('WD incorrect')
- elif I[0] == 4:
- problems.append('Error in derivatives')
- elif I[0] == 5:
- problems.append('Error occurred in callback')
- elif I[0] == 6:
- problems.append('Numerical error detected')
- return problems
- else:
- return [stopreason]
- class Data:
- """
- The data to fit.
- Parameters
- ----------
- x : array_like
- Observed data for the independent variable of the regression
- y : array_like, optional
- If array-like, observed data for the dependent variable of the
- regression. A scalar input implies that the model to be used on
- the data is implicit.
- we : array_like, optional
- If `we` is a scalar, then that value is used for all data points (and
- all dimensions of the response variable).
- If `we` is a rank-1 array of length q (the dimensionality of the
- response variable), then this vector is the diagonal of the covariant
- weighting matrix for all data points.
- If `we` is a rank-1 array of length n (the number of data points), then
- the i'th element is the weight for the i'th response variable
- observation (single-dimensional only).
- If `we` is a rank-2 array of shape (q, q), then this is the full
- covariant weighting matrix broadcast to each observation.
- If `we` is a rank-2 array of shape (q, n), then `we[:,i]` is the
- diagonal of the covariant weighting matrix for the i'th observation.
- If `we` is a rank-3 array of shape (q, q, n), then `we[:,:,i]` is the
- full specification of the covariant weighting matrix for each
- observation.
- If the fit is implicit, then only a positive scalar value is used.
- wd : array_like, optional
- If `wd` is a scalar, then that value is used for all data points
- (and all dimensions of the input variable). If `wd` = 0, then the
- covariant weighting matrix for each observation is set to the identity
- matrix (so each dimension of each observation has the same weight).
- If `wd` is a rank-1 array of length m (the dimensionality of the input
- variable), then this vector is the diagonal of the covariant weighting
- matrix for all data points.
- If `wd` is a rank-1 array of length n (the number of data points), then
- the i'th element is the weight for the ith input variable observation
- (single-dimensional only).
- If `wd` is a rank-2 array of shape (m, m), then this is the full
- covariant weighting matrix broadcast to each observation.
- If `wd` is a rank-2 array of shape (m, n), then `wd[:,i]` is the
- diagonal of the covariant weighting matrix for the ith observation.
- If `wd` is a rank-3 array of shape (m, m, n), then `wd[:,:,i]` is the
- full specification of the covariant weighting matrix for each
- observation.
- fix : array_like of ints, optional
- The `fix` argument is the same as ifixx in the class ODR. It is an
- array of integers with the same shape as data.x that determines which
- input observations are treated as fixed. One can use a sequence of
- length m (the dimensionality of the input observations) to fix some
- dimensions for all observations. A value of 0 fixes the observation,
- a value > 0 makes it free.
- meta : dict, optional
- Free-form dictionary for metadata.
- Notes
- -----
- Each argument is attached to the member of the instance of the same name.
- The structures of `x` and `y` are described in the Model class docstring.
- If `y` is an integer, then the Data instance can only be used to fit with
- implicit models where the dimensionality of the response is equal to the
- specified value of `y`.
- The `we` argument weights the effect a deviation in the response variable
- has on the fit. The `wd` argument weights the effect a deviation in the
- input variable has on the fit. To handle multidimensional inputs and
- responses easily, the structure of these arguments has the n'th
- dimensional axis first. These arguments heavily use the structured
- arguments feature of ODRPACK to conveniently and flexibly support all
- options. See the ODRPACK User's Guide for a full explanation of how these
- weights are used in the algorithm. Basically, a higher value of the weight
- for a particular data point makes a deviation at that point more
- detrimental to the fit.
- """
- def __init__(self, x, y=None, we=None, wd=None, fix=None, meta=None):
- self.x = _conv(x)
- if not isinstance(self.x, numpy.ndarray):
- raise ValueError(("Expected an 'ndarray' of data for 'x', "
- "but instead got data of type '{name}'").format(
- name=type(self.x).__name__))
- self.y = _conv(y)
- self.we = _conv(we)
- self.wd = _conv(wd)
- self.fix = _conv(fix)
- self.meta = {} if meta is None else meta
- def set_meta(self, **kwds):
- """ Update the metadata dictionary with the keywords and data provided
- by keywords.
- Examples
- --------
- ::
- data.set_meta(lab="Ph 7; Lab 26", title="Ag110 + Ag108 Decay")
- """
- self.meta.update(kwds)
- def __getattr__(self, attr):
- """ Dispatch attribute access to the metadata dictionary.
- """
- if attr in self.meta:
- return self.meta[attr]
- else:
- raise AttributeError("'%s' not in metadata" % attr)
- class RealData(Data):
- """
- The data, with weightings as actual standard deviations and/or
- covariances.
- Parameters
- ----------
- x : array_like
- Observed data for the independent variable of the regression
- y : array_like, optional
- If array-like, observed data for the dependent variable of the
- regression. A scalar input implies that the model to be used on
- the data is implicit.
- sx : array_like, optional
- Standard deviations of `x`.
- `sx` are standard deviations of `x` and are converted to weights by
- dividing 1.0 by their squares.
- sy : array_like, optional
- Standard deviations of `y`.
- `sy` are standard deviations of `y` and are converted to weights by
- dividing 1.0 by their squares.
- covx : array_like, optional
- Covariance of `x`
- `covx` is an array of covariance matrices of `x` and are converted to
- weights by performing a matrix inversion on each observation's
- covariance matrix.
- covy : array_like, optional
- Covariance of `y`
- `covy` is an array of covariance matrices and are converted to
- weights by performing a matrix inversion on each observation's
- covariance matrix.
- fix : array_like, optional
- The argument and member fix is the same as Data.fix and ODR.ifixx:
- It is an array of integers with the same shape as `x` that
- determines which input observations are treated as fixed. One can
- use a sequence of length m (the dimensionality of the input
- observations) to fix some dimensions for all observations. A value
- of 0 fixes the observation, a value > 0 makes it free.
- meta : dict, optional
- Free-form dictionary for metadata.
- Notes
- -----
- The weights `wd` and `we` are computed from provided values as follows:
- `sx` and `sy` are converted to weights by dividing 1.0 by their squares.
- For example, ``wd = 1./numpy.power(`sx`, 2)``.
- `covx` and `covy` are arrays of covariance matrices and are converted to
- weights by performing a matrix inversion on each observation's covariance
- matrix. For example, ``we[i] = numpy.linalg.inv(covy[i])``.
- These arguments follow the same structured argument conventions as wd and
- we only restricted by their natures: `sx` and `sy` can't be rank-3, but
- `covx` and `covy` can be.
- Only set *either* `sx` or `covx` (not both). Setting both will raise an
- exception. Same with `sy` and `covy`.
- """
- def __init__(self, x, y=None, sx=None, sy=None, covx=None, covy=None,
- fix=None, meta=None):
- if (sx is not None) and (covx is not None):
- raise ValueError("cannot set both sx and covx")
- if (sy is not None) and (covy is not None):
- raise ValueError("cannot set both sy and covy")
- # Set flags for __getattr__
- self._ga_flags = {}
- if sx is not None:
- self._ga_flags['wd'] = 'sx'
- else:
- self._ga_flags['wd'] = 'covx'
- if sy is not None:
- self._ga_flags['we'] = 'sy'
- else:
- self._ga_flags['we'] = 'covy'
- self.x = _conv(x)
- if not isinstance(self.x, numpy.ndarray):
- raise ValueError(("Expected an 'ndarray' of data for 'x', "
- "but instead got data of type '{name}'").format(
- name=type(self.x).__name__))
- self.y = _conv(y)
- self.sx = _conv(sx)
- self.sy = _conv(sy)
- self.covx = _conv(covx)
- self.covy = _conv(covy)
- self.fix = _conv(fix)
- self.meta = {} if meta is None else meta
- def _sd2wt(self, sd):
- """ Convert standard deviation to weights.
- """
- return 1./numpy.power(sd, 2)
- def _cov2wt(self, cov):
- """ Convert covariance matrix(-ices) to weights.
- """
- from scipy.linalg import inv
- if len(cov.shape) == 2:
- return inv(cov)
- else:
- weights = numpy.zeros(cov.shape, float)
- for i in range(cov.shape[-1]): # n
- weights[:,:,i] = inv(cov[:,:,i])
- return weights
- def __getattr__(self, attr):
- lookup_tbl = {('wd', 'sx'): (self._sd2wt, self.sx),
- ('wd', 'covx'): (self._cov2wt, self.covx),
- ('we', 'sy'): (self._sd2wt, self.sy),
- ('we', 'covy'): (self._cov2wt, self.covy)}
- if attr not in ('wd', 'we'):
- if attr in self.meta:
- return self.meta[attr]
- else:
- raise AttributeError("'%s' not in metadata" % attr)
- else:
- func, arg = lookup_tbl[(attr, self._ga_flags[attr])]
- if arg is not None:
- return func(*(arg,))
- else:
- return None
- class Model:
- """
- The Model class stores information about the function you wish to fit.
- It stores the function itself, at the least, and optionally stores
- functions which compute the Jacobians used during fitting. Also, one
- can provide a function that will provide reasonable starting values
- for the fit parameters possibly given the set of data.
- Parameters
- ----------
- fcn : function
- fcn(beta, x) --> y
- fjacb : function
- Jacobian of fcn wrt the fit parameters beta.
- fjacb(beta, x) --> @f_i(x,B)/@B_j
- fjacd : function
- Jacobian of fcn wrt the (possibly multidimensional) input
- variable.
- fjacd(beta, x) --> @f_i(x,B)/@x_j
- extra_args : tuple, optional
- If specified, `extra_args` should be a tuple of extra
- arguments to pass to `fcn`, `fjacb`, and `fjacd`. Each will be called
- by `apply(fcn, (beta, x) + extra_args)`
- estimate : array_like of rank-1
- Provides estimates of the fit parameters from the data
- estimate(data) --> estbeta
- implicit : boolean
- If TRUE, specifies that the model
- is implicit; i.e `fcn(beta, x)` ~= 0 and there is no y data to fit
- against
- meta : dict, optional
- freeform dictionary of metadata for the model
- Notes
- -----
- Note that the `fcn`, `fjacb`, and `fjacd` operate on NumPy arrays and
- return a NumPy array. The `estimate` object takes an instance of the
- Data class.
- Here are the rules for the shapes of the argument and return
- arrays of the callback functions:
- `x`
- if the input data is single-dimensional, then `x` is rank-1
- array; i.e., ``x = array([1, 2, 3, ...]); x.shape = (n,)``
- If the input data is multi-dimensional, then `x` is a rank-2 array;
- i.e., ``x = array([[1, 2, ...], [2, 4, ...]]); x.shape = (m, n)``.
- In all cases, it has the same shape as the input data array passed to
- `~scipy.odr.odr`. `m` is the dimensionality of the input data,
- `n` is the number of observations.
- `y`
- if the response variable is single-dimensional, then `y` is a
- rank-1 array, i.e., ``y = array([2, 4, ...]); y.shape = (n,)``.
- If the response variable is multi-dimensional, then `y` is a rank-2
- array, i.e., ``y = array([[2, 4, ...], [3, 6, ...]]); y.shape =
- (q, n)`` where `q` is the dimensionality of the response variable.
- `beta`
- rank-1 array of length `p` where `p` is the number of parameters;
- i.e. ``beta = array([B_1, B_2, ..., B_p])``
- `fjacb`
- if the response variable is multi-dimensional, then the
- return array's shape is `(q, p, n)` such that ``fjacb(x,beta)[l,k,i] =
- d f_l(X,B)/d B_k`` evaluated at the ith data point. If `q == 1`, then
- the return array is only rank-2 and with shape `(p, n)`.
- `fjacd`
- as with fjacb, only the return array's shape is `(q, m, n)`
- such that ``fjacd(x,beta)[l,j,i] = d f_l(X,B)/d X_j`` at the ith data
- point. If `q == 1`, then the return array's shape is `(m, n)`. If
- `m == 1`, the shape is (q, n). If `m == q == 1`, the shape is `(n,)`.
- """
- def __init__(self, fcn, fjacb=None, fjacd=None,
- extra_args=None, estimate=None, implicit=0, meta=None):
- self.fcn = fcn
- self.fjacb = fjacb
- self.fjacd = fjacd
- if extra_args is not None:
- extra_args = tuple(extra_args)
- self.extra_args = extra_args
- self.estimate = estimate
- self.implicit = implicit
- self.meta = meta if meta is not None else {}
- def set_meta(self, **kwds):
- """ Update the metadata dictionary with the keywords and data provided
- here.
- Examples
- --------
- set_meta(name="Exponential", equation="y = a exp(b x) + c")
- """
- self.meta.update(kwds)
- def __getattr__(self, attr):
- """ Dispatch attribute access to the metadata.
- """
- if attr in self.meta:
- return self.meta[attr]
- else:
- raise AttributeError("'%s' not in metadata" % attr)
- class Output:
- """
- The Output class stores the output of an ODR run.
- Attributes
- ----------
- beta : ndarray
- Estimated parameter values, of shape (q,).
- sd_beta : ndarray
- Standard deviations of the estimated parameters, of shape (p,).
- cov_beta : ndarray
- Covariance matrix of the estimated parameters, of shape (p,p).
- delta : ndarray, optional
- Array of estimated errors in input variables, of same shape as `x`.
- eps : ndarray, optional
- Array of estimated errors in response variables, of same shape as `y`.
- xplus : ndarray, optional
- Array of ``x + delta``.
- y : ndarray, optional
- Array ``y = fcn(x + delta)``.
- res_var : float, optional
- Residual variance.
- sum_square : float, optional
- Sum of squares error.
- sum_square_delta : float, optional
- Sum of squares of delta error.
- sum_square_eps : float, optional
- Sum of squares of eps error.
- inv_condnum : float, optional
- Inverse condition number (cf. ODRPACK UG p. 77).
- rel_error : float, optional
- Relative error in function values computed within fcn.
- work : ndarray, optional
- Final work array.
- work_ind : dict, optional
- Indices into work for drawing out values (cf. ODRPACK UG p. 83).
- info : int, optional
- Reason for returning, as output by ODRPACK (cf. ODRPACK UG p. 38).
- stopreason : list of str, optional
- `info` interpreted into English.
- Notes
- -----
- Takes one argument for initialization, the return value from the
- function `~scipy.odr.odr`. The attributes listed as "optional" above are
- only present if `~scipy.odr.odr` was run with ``full_output=1``.
- """
- def __init__(self, output):
- self.beta = output[0]
- self.sd_beta = output[1]
- self.cov_beta = output[2]
- if len(output) == 4:
- # full output
- self.__dict__.update(output[3])
- self.stopreason = _report_error(self.info)
- def pprint(self):
- """ Pretty-print important results.
- """
- print('Beta:', self.beta)
- print('Beta Std Error:', self.sd_beta)
- print('Beta Covariance:', self.cov_beta)
- if hasattr(self, 'info'):
- print('Residual Variance:',self.res_var)
- print('Inverse Condition #:', self.inv_condnum)
- print('Reason(s) for Halting:')
- for r in self.stopreason:
- print(' %s' % r)
- class ODR:
- """
- The ODR class gathers all information and coordinates the running of the
- main fitting routine.
- Members of instances of the ODR class have the same names as the arguments
- to the initialization routine.
- Parameters
- ----------
- data : Data class instance
- instance of the Data class
- model : Model class instance
- instance of the Model class
- Other Parameters
- ----------------
- beta0 : array_like of rank-1
- a rank-1 sequence of initial parameter values. Optional if
- model provides an "estimate" function to estimate these values.
- delta0 : array_like of floats of rank-1, optional
- a (double-precision) float array to hold the initial values of
- the errors in the input variables. Must be same shape as data.x
- ifixb : array_like of ints of rank-1, optional
- sequence of integers with the same length as beta0 that determines
- which parameters are held fixed. A value of 0 fixes the parameter,
- a value > 0 makes the parameter free.
- ifixx : array_like of ints with same shape as data.x, optional
- an array of integers with the same shape as data.x that determines
- which input observations are treated as fixed. One can use a sequence
- of length m (the dimensionality of the input observations) to fix some
- dimensions for all observations. A value of 0 fixes the observation,
- a value > 0 makes it free.
- job : int, optional
- an integer telling ODRPACK what tasks to perform. See p. 31 of the
- ODRPACK User's Guide if you absolutely must set the value here. Use the
- method set_job post-initialization for a more readable interface.
- iprint : int, optional
- an integer telling ODRPACK what to print. See pp. 33-34 of the
- ODRPACK User's Guide if you absolutely must set the value here. Use the
- method set_iprint post-initialization for a more readable interface.
- errfile : str, optional
- string with the filename to print ODRPACK errors to. If the file already
- exists, an error will be thrown. The `overwrite` argument can be used to
- prevent this. *Do Not Open This File Yourself!*
- rptfile : str, optional
- string with the filename to print ODRPACK summaries to. If the file
- already exists, an error will be thrown. The `overwrite` argument can be
- used to prevent this. *Do Not Open This File Yourself!*
- ndigit : int, optional
- integer specifying the number of reliable digits in the computation
- of the function.
- taufac : float, optional
- float specifying the initial trust region. The default value is 1.
- The initial trust region is equal to taufac times the length of the
- first computed Gauss-Newton step. taufac must be less than 1.
- sstol : float, optional
- float specifying the tolerance for convergence based on the relative
- change in the sum-of-squares. The default value is eps**(1/2) where eps
- is the smallest value such that 1 + eps > 1 for double precision
- computation on the machine. sstol must be less than 1.
- partol : float, optional
- float specifying the tolerance for convergence based on the relative
- change in the estimated parameters. The default value is eps**(2/3) for
- explicit models and ``eps**(1/3)`` for implicit models. partol must be less
- than 1.
- maxit : int, optional
- integer specifying the maximum number of iterations to perform. For
- first runs, maxit is the total number of iterations performed and
- defaults to 50. For restarts, maxit is the number of additional
- iterations to perform and defaults to 10.
- stpb : array_like, optional
- sequence (``len(stpb) == len(beta0)``) of relative step sizes to compute
- finite difference derivatives wrt the parameters.
- stpd : optional
- array (``stpd.shape == data.x.shape`` or ``stpd.shape == (m,)``) of relative
- step sizes to compute finite difference derivatives wrt the input
- variable errors. If stpd is a rank-1 array with length m (the
- dimensionality of the input variable), then the values are broadcast to
- all observations.
- sclb : array_like, optional
- sequence (``len(stpb) == len(beta0)``) of scaling factors for the
- parameters. The purpose of these scaling factors are to scale all of
- the parameters to around unity. Normally appropriate scaling factors
- are computed if this argument is not specified. Specify them yourself
- if the automatic procedure goes awry.
- scld : array_like, optional
- array (scld.shape == data.x.shape or scld.shape == (m,)) of scaling
- factors for the *errors* in the input variables. Again, these factors
- are automatically computed if you do not provide them. If scld.shape ==
- (m,), then the scaling factors are broadcast to all observations.
- work : ndarray, optional
- array to hold the double-valued working data for ODRPACK. When
- restarting, takes the value of self.output.work.
- iwork : ndarray, optional
- array to hold the integer-valued working data for ODRPACK. When
- restarting, takes the value of self.output.iwork.
- overwrite : bool, optional
- If it is True, output files defined by `errfile` and `rptfile` are
- overwritten. The default is False.
- Attributes
- ----------
- data : Data
- The data for this fit
- model : Model
- The model used in fit
- output : Output
- An instance if the Output class containing all of the returned
- data from an invocation of ODR.run() or ODR.restart()
- """
- def __init__(self, data, model, beta0=None, delta0=None, ifixb=None,
- ifixx=None, job=None, iprint=None, errfile=None, rptfile=None,
- ndigit=None, taufac=None, sstol=None, partol=None, maxit=None,
- stpb=None, stpd=None, sclb=None, scld=None, work=None, iwork=None,
- overwrite=False):
- self.data = data
- self.model = model
- if beta0 is None:
- if self.model.estimate is not None:
- self.beta0 = _conv(self.model.estimate(self.data))
- else:
- raise ValueError(
- "must specify beta0 or provide an estimater with the model"
- )
- else:
- self.beta0 = _conv(beta0)
- if ifixx is None and data.fix is not None:
- ifixx = data.fix
- if overwrite:
- # remove output files for overwriting.
- if rptfile is not None and os.path.exists(rptfile):
- os.remove(rptfile)
- if errfile is not None and os.path.exists(errfile):
- os.remove(errfile)
- self.delta0 = _conv(delta0)
- # These really are 32-bit integers in FORTRAN (gfortran), even on 64-bit
- # platforms.
- # XXX: some other FORTRAN compilers may not agree.
- self.ifixx = _conv(ifixx, dtype=numpy.int32)
- self.ifixb = _conv(ifixb, dtype=numpy.int32)
- self.job = job
- self.iprint = iprint
- self.errfile = errfile
- self.rptfile = rptfile
- self.ndigit = ndigit
- self.taufac = taufac
- self.sstol = sstol
- self.partol = partol
- self.maxit = maxit
- self.stpb = _conv(stpb)
- self.stpd = _conv(stpd)
- self.sclb = _conv(sclb)
- self.scld = _conv(scld)
- self.work = _conv(work)
- self.iwork = _conv(iwork)
- self.output = None
- self._check()
- def _check(self):
- """ Check the inputs for consistency, but don't bother checking things
- that the builtin function odr will check.
- """
- x_s = list(self.data.x.shape)
- if isinstance(self.data.y, numpy.ndarray):
- y_s = list(self.data.y.shape)
- if self.model.implicit:
- raise OdrError("an implicit model cannot use response data")
- else:
- # implicit model with q == self.data.y
- y_s = [self.data.y, x_s[-1]]
- if not self.model.implicit:
- raise OdrError("an explicit model needs response data")
- self.set_job(fit_type=1)
- if x_s[-1] != y_s[-1]:
- raise OdrError("number of observations do not match")
- n = x_s[-1]
- if len(x_s) == 2:
- m = x_s[0]
- else:
- m = 1
- if len(y_s) == 2:
- q = y_s[0]
- else:
- q = 1
- p = len(self.beta0)
- # permissible output array shapes
- fcn_perms = [(q, n)]
- fjacd_perms = [(q, m, n)]
- fjacb_perms = [(q, p, n)]
- if q == 1:
- fcn_perms.append((n,))
- fjacd_perms.append((m, n))
- fjacb_perms.append((p, n))
- if m == 1:
- fjacd_perms.append((q, n))
- if p == 1:
- fjacb_perms.append((q, n))
- if m == q == 1:
- fjacd_perms.append((n,))
- if p == q == 1:
- fjacb_perms.append((n,))
- # try evaluating the supplied functions to make sure they provide
- # sensible outputs
- arglist = (self.beta0, self.data.x)
- if self.model.extra_args is not None:
- arglist = arglist + self.model.extra_args
- res = self.model.fcn(*arglist)
- if res.shape not in fcn_perms:
- print(res.shape)
- print(fcn_perms)
- raise OdrError("fcn does not output %s-shaped array" % y_s)
- if self.model.fjacd is not None:
- res = self.model.fjacd(*arglist)
- if res.shape not in fjacd_perms:
- raise OdrError(
- "fjacd does not output %s-shaped array" % repr((q, m, n)))
- if self.model.fjacb is not None:
- res = self.model.fjacb(*arglist)
- if res.shape not in fjacb_perms:
- raise OdrError(
- "fjacb does not output %s-shaped array" % repr((q, p, n)))
- # check shape of delta0
- if self.delta0 is not None and self.delta0.shape != self.data.x.shape:
- raise OdrError(
- "delta0 is not a %s-shaped array" % repr(self.data.x.shape))
- if self.data.x.size == 0:
- warn(("Empty data detected for ODR instance. "
- "Do not expect any fitting to occur"),
- OdrWarning)
- def _gen_work(self):
- """ Generate a suitable work array if one does not already exist.
- """
- n = self.data.x.shape[-1]
- p = self.beta0.shape[0]
- if len(self.data.x.shape) == 2:
- m = self.data.x.shape[0]
- else:
- m = 1
- if self.model.implicit:
- q = self.data.y
- elif len(self.data.y.shape) == 2:
- q = self.data.y.shape[0]
- else:
- q = 1
- if self.data.we is None:
- ldwe = ld2we = 1
- elif len(self.data.we.shape) == 3:
- ld2we, ldwe = self.data.we.shape[1:]
- else:
- # Okay, this isn't precisely right, but for this calculation,
- # it's fine
- ldwe = 1
- ld2we = self.data.we.shape[1]
- if self.job % 10 < 2:
- # ODR not OLS
- lwork = (18 + 11*p + p*p + m + m*m + 4*n*q + 6*n*m + 2*n*q*p +
- 2*n*q*m + q*q + 5*q + q*(p+m) + ldwe*ld2we*q)
- else:
- # OLS not ODR
- lwork = (18 + 11*p + p*p + m + m*m + 4*n*q + 2*n*m + 2*n*q*p +
- 5*q + q*(p+m) + ldwe*ld2we*q)
- if isinstance(self.work, numpy.ndarray) and self.work.shape == (lwork,)\
- and self.work.dtype.str.endswith('f8'):
- # the existing array is fine
- return
- else:
- self.work = numpy.zeros((lwork,), float)
- def set_job(self, fit_type=None, deriv=None, var_calc=None,
- del_init=None, restart=None):
- """
- Sets the "job" parameter is a hopefully comprehensible way.
- If an argument is not specified, then the value is left as is. The
- default value from class initialization is for all of these options set
- to 0.
- Parameters
- ----------
- fit_type : {0, 1, 2} int
- 0 -> explicit ODR
- 1 -> implicit ODR
- 2 -> ordinary least-squares
- deriv : {0, 1, 2, 3} int
- 0 -> forward finite differences
- 1 -> central finite differences
- 2 -> user-supplied derivatives (Jacobians) with results
- checked by ODRPACK
- 3 -> user-supplied derivatives, no checking
- var_calc : {0, 1, 2} int
- 0 -> calculate asymptotic covariance matrix and fit
- parameter uncertainties (V_B, s_B) using derivatives
- recomputed at the final solution
- 1 -> calculate V_B and s_B using derivatives from last iteration
- 2 -> do not calculate V_B and s_B
- del_init : {0, 1} int
- 0 -> initial input variable offsets set to 0
- 1 -> initial offsets provided by user in variable "work"
- restart : {0, 1} int
- 0 -> fit is not a restart
- 1 -> fit is a restart
- Notes
- -----
- The permissible values are different from those given on pg. 31 of the
- ODRPACK User's Guide only in that one cannot specify numbers greater than
- the last value for each variable.
- If one does not supply functions to compute the Jacobians, the fitting
- procedure will change deriv to 0, finite differences, as a default. To
- initialize the input variable offsets by yourself, set del_init to 1 and
- put the offsets into the "work" variable correctly.
- """
- if self.job is None:
- job_l = [0, 0, 0, 0, 0]
- else:
- job_l = [self.job // 10000 % 10,
- self.job // 1000 % 10,
- self.job // 100 % 10,
- self.job // 10 % 10,
- self.job % 10]
- if fit_type in (0, 1, 2):
- job_l[4] = fit_type
- if deriv in (0, 1, 2, 3):
- job_l[3] = deriv
- if var_calc in (0, 1, 2):
- job_l[2] = var_calc
- if del_init in (0, 1):
- job_l[1] = del_init
- if restart in (0, 1):
- job_l[0] = restart
- self.job = (job_l[0]*10000 + job_l[1]*1000 +
- job_l[2]*100 + job_l[3]*10 + job_l[4])
- def set_iprint(self, init=None, so_init=None,
- iter=None, so_iter=None, iter_step=None, final=None, so_final=None):
- """ Set the iprint parameter for the printing of computation reports.
- If any of the arguments are specified here, then they are set in the
- iprint member. If iprint is not set manually or with this method, then
- ODRPACK defaults to no printing. If no filename is specified with the
- member rptfile, then ODRPACK prints to stdout. One can tell ODRPACK to
- print to stdout in addition to the specified filename by setting the
- so_* arguments to this function, but one cannot specify to print to
- stdout but not a file since one can do that by not specifying a rptfile
- filename.
- There are three reports: initialization, iteration, and final reports.
- They are represented by the arguments init, iter, and final
- respectively. The permissible values are 0, 1, and 2 representing "no
- report", "short report", and "long report" respectively.
- The argument iter_step (0 <= iter_step <= 9) specifies how often to make
- the iteration report; the report will be made for every iter_step'th
- iteration starting with iteration one. If iter_step == 0, then no
- iteration report is made, regardless of the other arguments.
- If the rptfile is None, then any so_* arguments supplied will raise an
- exception.
- """
- if self.iprint is None:
- self.iprint = 0
- ip = [self.iprint // 1000 % 10,
- self.iprint // 100 % 10,
- self.iprint // 10 % 10,
- self.iprint % 10]
- # make a list to convert iprint digits to/from argument inputs
- # rptfile, stdout
- ip2arg = [[0, 0], # none, none
- [1, 0], # short, none
- [2, 0], # long, none
- [1, 1], # short, short
- [2, 1], # long, short
- [1, 2], # short, long
- [2, 2]] # long, long
- if (self.rptfile is None and
- (so_init is not None or
- so_iter is not None or
- so_final is not None)):
- raise OdrError(
- "no rptfile specified, cannot output to stdout twice")
- iprint_l = ip2arg[ip[0]] + ip2arg[ip[1]] + ip2arg[ip[3]]
- if init is not None:
- iprint_l[0] = init
- if so_init is not None:
- iprint_l[1] = so_init
- if iter is not None:
- iprint_l[2] = iter
- if so_iter is not None:
- iprint_l[3] = so_iter
- if final is not None:
- iprint_l[4] = final
- if so_final is not None:
- iprint_l[5] = so_final
- if iter_step in range(10):
- # 0..9
- ip[2] = iter_step
- ip[0] = ip2arg.index(iprint_l[0:2])
- ip[1] = ip2arg.index(iprint_l[2:4])
- ip[3] = ip2arg.index(iprint_l[4:6])
- self.iprint = ip[0]*1000 + ip[1]*100 + ip[2]*10 + ip[3]
- def run(self):
- """ Run the fitting routine with all of the information given and with ``full_output=1``.
- Returns
- -------
- output : Output instance
- This object is also assigned to the attribute .output .
- """
- args = (self.model.fcn, self.beta0, self.data.y, self.data.x)
- kwds = {'full_output': 1}
- kwd_l = ['ifixx', 'ifixb', 'job', 'iprint', 'errfile', 'rptfile',
- 'ndigit', 'taufac', 'sstol', 'partol', 'maxit', 'stpb',
- 'stpd', 'sclb', 'scld', 'work', 'iwork']
- if self.delta0 is not None and (self.job // 10000) % 10 == 0:
- # delta0 provided and fit is not a restart
- self._gen_work()
- d0 = numpy.ravel(self.delta0)
- self.work[:len(d0)] = d0
- # set the kwds from other objects explicitly
- if self.model.fjacb is not None:
- kwds['fjacb'] = self.model.fjacb
- if self.model.fjacd is not None:
- kwds['fjacd'] = self.model.fjacd
- if self.data.we is not None:
- kwds['we'] = self.data.we
- if self.data.wd is not None:
- kwds['wd'] = self.data.wd
- if self.model.extra_args is not None:
- kwds['extra_args'] = self.model.extra_args
- # implicitly set kwds from self's members
- for attr in kwd_l:
- obj = getattr(self, attr)
- if obj is not None:
- kwds[attr] = obj
- self.output = Output(odr(*args, **kwds))
- return self.output
- def restart(self, iter=None):
- """ Restarts the run with iter more iterations.
- Parameters
- ----------
- iter : int, optional
- ODRPACK's default for the number of new iterations is 10.
- Returns
- -------
- output : Output instance
- This object is also assigned to the attribute .output .
- """
- if self.output is None:
- raise OdrError("cannot restart: run() has not been called before")
- self.set_job(restart=1)
- self.work = self.output.work
- self.iwork = self.output.iwork
- self.maxit = iter
- return self.run()
|