| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200 |
- """
- 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
- from threading import Lock
- import numpy as np
- 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
- ODR_LOCK = Lock()
- 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.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- """
- pass
- class OdrError(Exception):
- """
- Exception indicating an error in fitting.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- This is raised by `~scipy.odr.odr` if an error occurs during fitting.
- """
- pass
- class OdrStop(Exception):
- """
- Exception stopping fitting.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- 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 = np.asarray(obj)
- else:
- obj = np.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.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- 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, np.ndarray):
- raise ValueError("Expected an 'ndarray' of data for 'x', "
- f"but instead got data of type '{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 != "meta" and attr in self.meta:
- return self.meta[attr]
- else:
- raise AttributeError(f"'{attr}' not in metadata")
- class RealData(Data):
- """
- The data, with weightings as actual standard deviations and/or
- covariances.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- 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./np.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] = np.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, np.ndarray):
- raise ValueError("Expected an 'ndarray' of data for 'x', "
- f"but instead got data of type '{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./np.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 = np.zeros(cov.shape, float)
- for i in range(cov.shape[-1]): # n
- weights[:,:,i] = inv(cov[:,:,i])
- return weights
- def __getattr__(self, attr):
- if attr not in ('wd', 'we'):
- if attr != "meta" and attr in self.meta:
- return self.meta[attr]
- else:
- raise AttributeError(f"'{attr}' not in metadata")
- else:
- 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)}
- 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.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- 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(beta,x)[l,k,i] =
- d f_l(beta,x)/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(beta,x)[l,j,i] = d f_l(beta,x)/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 != "meta" and attr in self.meta:
- return self.meta[attr]
- else:
- raise AttributeError(f"'{attr}' not in metadata")
- class Output:
- """
- The Output class stores the output of an ODR run.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- 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).
- Note that this `cov_beta` is not scaled by the residual variance
- `res_var`, whereas `sd_beta` is. This means
- ``np.sqrt(np.diag(output.cov_beta * output.res_var))`` is the same
- result as `output.sd_beta`.
- 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(f' {r}')
- class ODR:
- """
- The ODR class gathers all information and coordinates the running of the
- main fitting routine.
- .. deprecated:: 1.17.0
- `scipy.odr` is deprecated and will be removed in SciPy 1.19.0. Please use
- `pypi.org/project/odrpack/ <https://pypi.org/project/odrpack/>`_
- instead.
- 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 estimator 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=np.int32)
- self.ifixb = _conv(ifixb, dtype=np.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, np.ndarray):
- y_s = list(self.data.y.shape)
- if self.model.implicit:
- raise OdrError("an implicit model cannot use response data")
- if self.job is not None and (self.job % 10) == 1:
- raise OdrError("job parameter requests an implicit model,"
- " but an explicit model was passed")
- 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(f"fcn does not output {y_s}-shaped array")
- if self.model.fjacd is not None:
- res = self.model.fjacd(*arglist)
- if res.shape not in fjacd_perms:
- raise OdrError(
- f"fjacd does not output {repr((q, m, n))}-shaped array")
- if self.model.fjacb is not None:
- res = self.model.fjacb(*arglist)
- if res.shape not in fjacb_perms:
- raise OdrError(
- f"fjacb does not output {repr((q, p, n))}-shaped array")
- # check shape of delta0
- if self.delta0 is not None and self.delta0.shape != self.data.x.shape:
- raise OdrError(
- f"delta0 is not a {repr(self.data.x.shape)}-shaped array")
- if self.data.x.size == 0:
- warn("Empty data detected for ODR instance. "
- "Do not expect any fitting to occur",
- OdrWarning, stacklevel=3)
- 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:
- we = self.data.we
- ldwe = 1
- ld2we = 1
- if we.ndim == 1 and q == 1:
- ldwe = n
- elif we.ndim == 2:
- if we.shape == (q, q):
- ld2we = q
- elif we.shape == (q, n):
- ldwe = n
- 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, np.ndarray) and self.work.shape == (lwork,)\
- and self.work.dtype.str.endswith('f8'):
- # the existing array is fine
- return
- else:
- self.work = np.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 .
- """ # noqa: E501
- 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 = np.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
- with ODR_LOCK:
- 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()
|